@dreamtree-org/twreact-ui 1.2.1 → 1.3.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.
Files changed (215) hide show
  1. package/DESIGN.md +118 -0
  2. package/README.md +231 -44
  3. package/ai-skills/dreamtree-ui.md +98 -12
  4. package/dist/ai-schema.json +452 -17
  5. package/dist/components/blocks/AppShell.d.ts +4 -0
  6. package/dist/components/blocks/AppShell.d.ts.map +1 -0
  7. package/dist/components/blocks/ConfirmDialog.d.ts +4 -0
  8. package/dist/components/blocks/ConfirmDialog.d.ts.map +1 -0
  9. package/dist/components/blocks/EmptyState.d.ts +4 -0
  10. package/dist/components/blocks/EmptyState.d.ts.map +1 -0
  11. package/dist/components/blocks/ErrorState.d.ts +4 -0
  12. package/dist/components/blocks/ErrorState.d.ts.map +1 -0
  13. package/dist/components/blocks/LoadingState.d.ts +9 -0
  14. package/dist/components/blocks/LoadingState.d.ts.map +1 -0
  15. package/dist/components/blocks/PageHeader.d.ts +4 -0
  16. package/dist/components/blocks/PageHeader.d.ts.map +1 -0
  17. package/dist/components/blocks/StatCard.d.ts +4 -0
  18. package/dist/components/blocks/StatCard.d.ts.map +1 -0
  19. package/dist/components/blocks/StatusPill.d.ts +5 -0
  20. package/dist/components/blocks/StatusPill.d.ts.map +1 -0
  21. package/dist/components/core/Accordion.d.ts.map +1 -1
  22. package/dist/components/core/Button.d.ts.map +1 -1
  23. package/dist/components/core/Checkbox.d.ts.map +1 -1
  24. package/dist/components/core/ColorPicker.d.ts.map +1 -1
  25. package/dist/components/core/DatePicker.d.ts.map +1 -1
  26. package/dist/components/core/DateRangePicker.d.ts.map +1 -1
  27. package/dist/components/core/Form.d.ts.map +1 -1
  28. package/dist/components/core/Input.d.ts.map +1 -1
  29. package/dist/components/core/LocationPicker.d.ts.map +1 -1
  30. package/dist/components/core/Popover.d.ts +4 -0
  31. package/dist/components/core/Popover.d.ts.map +1 -0
  32. package/dist/components/core/PriceRangePicker.d.ts.map +1 -1
  33. package/dist/components/core/ProgressBar.d.ts.map +1 -1
  34. package/dist/components/core/Radio.d.ts.map +1 -1
  35. package/dist/components/core/Rate.d.ts.map +1 -1
  36. package/dist/components/core/Select.d.ts.map +1 -1
  37. package/dist/components/core/Skeleton.d.ts.map +1 -1
  38. package/dist/components/core/Slider.d.ts +4 -0
  39. package/dist/components/core/Slider.d.ts.map +1 -0
  40. package/dist/components/core/SpeechToText.d.ts.map +1 -1
  41. package/dist/components/core/Switch.d.ts.map +1 -1
  42. package/dist/components/core/Table/ActionMenu.d.ts.map +1 -1
  43. package/dist/components/core/Table/ColumnMenu.d.ts +2 -1
  44. package/dist/components/core/Table/ColumnMenu.d.ts.map +1 -1
  45. package/dist/components/core/Table/MobileCard.d.ts +2 -2
  46. package/dist/components/core/Table/MobileCard.d.ts.map +1 -1
  47. package/dist/components/core/Table/Table.d.ts.map +1 -1
  48. package/dist/components/core/Table/TableRow.d.ts +4 -2
  49. package/dist/components/core/Table/TableRow.d.ts.map +1 -1
  50. package/dist/components/core/Tabs.d.ts.map +1 -1
  51. package/dist/components/core/Textarea.d.ts +4 -0
  52. package/dist/components/core/Textarea.d.ts.map +1 -0
  53. package/dist/components/core/ThreeDotPopover.d.ts.map +1 -1
  54. package/dist/components/core/Tooltip.d.ts.map +1 -1
  55. package/dist/components/feedback/Dialog.d.ts.map +1 -1
  56. package/dist/components/feedback/Drawer.d.ts +4 -0
  57. package/dist/components/feedback/Drawer.d.ts.map +1 -0
  58. package/dist/components/feedback/Toast.d.ts +14 -5
  59. package/dist/components/feedback/Toast.d.ts.map +1 -1
  60. package/dist/components/feedback/Tour.d.ts +4 -0
  61. package/dist/components/feedback/Tour.d.ts.map +1 -0
  62. package/dist/components/feedback/TourSpotlight.d.ts +9 -0
  63. package/dist/components/feedback/TourSpotlight.d.ts.map +1 -0
  64. package/dist/components/navigation/Breadcrumbs.d.ts.map +1 -1
  65. package/dist/components/navigation/FootNav.d.ts.map +1 -1
  66. package/dist/components/navigation/Navbar.d.ts.map +1 -1
  67. package/dist/components/navigation/Sidebar.d.ts +8 -2
  68. package/dist/components/navigation/Sidebar.d.ts.map +1 -1
  69. package/dist/components/utility/Badge.d.ts.map +1 -1
  70. package/dist/components/utility/FileUpload.d.ts.map +1 -1
  71. package/dist/components/utility/Sortable.d.ts +4 -0
  72. package/dist/components/utility/Sortable.d.ts.map +1 -0
  73. package/dist/esm/_vendor/object-assign/index.js +99 -0
  74. package/dist/esm/_vendor/prop-types/_vendor/react-is/cjs/react-is.development.js +190 -0
  75. package/dist/esm/_vendor/prop-types/_vendor/react-is/cjs/react-is.production.min.js +26 -0
  76. package/dist/esm/_vendor/prop-types/_vendor/react-is/index.js +19 -0
  77. package/dist/esm/_vendor/prop-types/checkPropTypes.js +115 -0
  78. package/dist/esm/_vendor/prop-types/factoryWithThrowingShims.js +74 -0
  79. package/dist/esm/_vendor/prop-types/factoryWithTypeCheckers.js +625 -0
  80. package/dist/esm/_vendor/prop-types/index.js +30 -0
  81. package/dist/esm/_vendor/prop-types/lib/ReactPropTypesSecret.js +21 -0
  82. package/dist/esm/_vendor/prop-types/lib/has.js +11 -0
  83. package/dist/esm/_virtual/_commonjsHelpers.js +5 -0
  84. package/dist/esm/_virtual/_rollupPluginBabelHelpers.js +329 -0
  85. package/dist/esm/_virtual/index.js +3 -0
  86. package/dist/esm/_virtual/index2.js +3 -0
  87. package/dist/esm/_virtual/react-is.development.js +3 -0
  88. package/dist/esm/_virtual/react-is.production.min.js +3 -0
  89. package/dist/esm/components/blocks/AppShell.js +205 -0
  90. package/dist/esm/components/blocks/ConfirmDialog.js +57 -0
  91. package/dist/esm/components/blocks/EmptyState.js +44 -0
  92. package/dist/esm/components/blocks/ErrorState.js +53 -0
  93. package/dist/esm/components/blocks/LoadingState.js +134 -0
  94. package/dist/esm/components/blocks/PageHeader.js +51 -0
  95. package/dist/esm/components/blocks/StatCard.js +79 -0
  96. package/dist/esm/components/blocks/StatusPill.js +55 -0
  97. package/dist/esm/components/core/Accordion.js +260 -0
  98. package/dist/esm/components/core/Button.js +59 -0
  99. package/dist/esm/components/core/Checkbox.js +188 -0
  100. package/dist/esm/components/core/ColorPicker.js +348 -0
  101. package/dist/esm/components/core/DatePicker.js +436 -0
  102. package/dist/esm/components/core/DateRangePicker.js +494 -0
  103. package/dist/esm/components/core/Form.js +522 -0
  104. package/dist/esm/components/core/Input.js +179 -0
  105. package/dist/esm/components/core/Loader.js +141 -0
  106. package/dist/esm/components/core/LocationPicker.js +161 -0
  107. package/dist/esm/components/core/Popover.js +171 -0
  108. package/dist/esm/components/core/PriceRangePicker.js +376 -0
  109. package/dist/esm/components/core/ProgressBar.js +152 -0
  110. package/dist/esm/components/core/Radio.js +156 -0
  111. package/dist/esm/components/core/Rate.js +197 -0
  112. package/dist/esm/components/core/RoundedTag.js +118 -0
  113. package/dist/esm/components/core/Select.js +857 -0
  114. package/dist/esm/components/core/Skeleton.js +100 -0
  115. package/dist/esm/components/core/Slider.js +134 -0
  116. package/dist/esm/components/core/SpeechToText.js +226 -0
  117. package/dist/esm/components/core/Switch.js +207 -0
  118. package/dist/esm/components/core/Table/ActionMenu.js +251 -0
  119. package/dist/esm/components/core/Table/ColumnMenu.js +55 -0
  120. package/dist/esm/components/core/Table/FieldInput.js +157 -0
  121. package/dist/esm/components/core/Table/MobileCard.js +187 -0
  122. package/dist/esm/components/core/Table/MobileFilters.js +72 -0
  123. package/dist/esm/components/core/Table/Pagination.js +157 -0
  124. package/dist/esm/components/core/Table/Table.js +1533 -0
  125. package/dist/esm/components/core/Table/TableRow.js +254 -0
  126. package/dist/esm/components/core/Table/constants.js +35 -0
  127. package/dist/esm/components/core/Table/helpers.js +124 -0
  128. package/dist/esm/components/core/Table/useClickOutside.js +26 -0
  129. package/dist/esm/components/core/Tabs.js +289 -0
  130. package/dist/esm/components/core/TextToSpeech.js +52 -0
  131. package/dist/esm/components/core/Textarea.js +125 -0
  132. package/dist/esm/components/core/ThreeDotPopover.js +160 -0
  133. package/dist/esm/components/core/Tooltip.js +372 -0
  134. package/dist/esm/components/feedback/Alert.js +96 -0
  135. package/dist/esm/components/feedback/Dialog.js +200 -0
  136. package/dist/esm/components/feedback/Drawer.js +171 -0
  137. package/dist/esm/components/feedback/Toast.js +362 -0
  138. package/dist/esm/components/feedback/Tour.js +214 -0
  139. package/dist/esm/components/feedback/TourSpotlight.js +71 -0
  140. package/dist/esm/components/navigation/Breadcrumbs.js +75 -0
  141. package/dist/esm/components/navigation/Dropdown.js +53 -0
  142. package/dist/esm/components/navigation/FootNav.js +51 -0
  143. package/dist/esm/components/navigation/Navbar.js +437 -0
  144. package/dist/esm/components/navigation/Sidebar.js +567 -0
  145. package/dist/esm/components/utility/Avatar.js +89 -0
  146. package/dist/esm/components/utility/Badge.js +47 -0
  147. package/dist/esm/components/utility/Card.js +85 -0
  148. package/dist/esm/components/utility/Carousel.js +97 -0
  149. package/dist/esm/components/utility/Condition.js +9 -0
  150. package/dist/esm/components/utility/FileUpload.js +177 -0
  151. package/dist/esm/components/utility/Pagination.js +111 -0
  152. package/dist/esm/components/utility/Sortable.js +190 -0
  153. package/dist/esm/components/utility/Stepper.js +101 -0
  154. package/dist/esm/hooks/useApi.js +169 -0
  155. package/dist/esm/hooks/useControllableState.js +32 -0
  156. package/dist/esm/hooks/useFocusTrap.js +85 -0
  157. package/dist/esm/hooks/useLatestRef.js +20 -0
  158. package/dist/esm/hooks/useMixins.js +100 -0
  159. package/dist/esm/hooks/useScrollLock.js +26 -0
  160. package/dist/esm/hooks/useSliderValues.js +135 -0
  161. package/dist/esm/hooks/useSortable.js +188 -0
  162. package/dist/esm/hooks/useTheme.js +53 -0
  163. package/dist/esm/hooks/useTourTarget.js +92 -0
  164. package/dist/esm/index.js +65 -0
  165. package/dist/esm/store/index.js +26 -0
  166. package/dist/esm/store/store.js +107 -0
  167. package/dist/esm/store/storeSlice.js +45 -0
  168. package/dist/esm/utils/cn.js +11 -0
  169. package/dist/esm/utils/emitter.js +169 -0
  170. package/dist/esm/utils/helpers.js +161 -0
  171. package/dist/esm/utils/position.js +134 -0
  172. package/dist/hooks/useApi.d.ts +2 -1
  173. package/dist/hooks/useApi.d.ts.map +1 -1
  174. package/dist/hooks/useControllableState.d.ts +11 -0
  175. package/dist/hooks/useControllableState.d.ts.map +1 -0
  176. package/dist/hooks/useFocusTrap.d.ts +15 -0
  177. package/dist/hooks/useFocusTrap.d.ts.map +1 -0
  178. package/dist/hooks/useLatestRef.d.ts +8 -0
  179. package/dist/hooks/useLatestRef.d.ts.map +1 -0
  180. package/dist/hooks/useMixins.d.ts.map +1 -1
  181. package/dist/hooks/useScrollLock.d.ts +2 -0
  182. package/dist/hooks/useScrollLock.d.ts.map +1 -0
  183. package/dist/hooks/useSliderValues.d.ts +43 -0
  184. package/dist/hooks/useSliderValues.d.ts.map +1 -0
  185. package/dist/hooks/useSortable.d.ts +32 -0
  186. package/dist/hooks/useSortable.d.ts.map +1 -0
  187. package/dist/hooks/useTheme.d.ts.map +1 -1
  188. package/dist/hooks/useTourTarget.d.ts +15 -0
  189. package/dist/hooks/useTourTarget.d.ts.map +1 -0
  190. package/dist/index.css +1 -1
  191. package/dist/index.d.ts +55 -39
  192. package/dist/index.esm.css +1 -1
  193. package/dist/index.esm.js +7937 -4659
  194. package/dist/index.js +7950 -4657
  195. package/dist/tokens/brands/default.css +1 -0
  196. package/dist/tokens/brands/default.json +5 -0
  197. package/dist/tokens/brands/practicepro.css +17 -0
  198. package/dist/tokens/brands/practicepro.json +27 -0
  199. package/dist/tokens/tailwind3-preset.cjs +214 -0
  200. package/dist/tokens/theme.css +193 -0
  201. package/dist/tokens/tokens.json +373 -0
  202. package/dist/tokens/vars.css +98 -0
  203. package/dist/types/blocks.d.ts +109 -0
  204. package/dist/types/blocks.d.ts.map +1 -0
  205. package/dist/types/index.d.ts +194 -5
  206. package/dist/types/index.d.ts.map +1 -1
  207. package/dist/utils/position.d.ts +30 -0
  208. package/dist/utils/position.d.ts.map +1 -0
  209. package/doc/README.md +26 -1
  210. package/mcp/catalog.snapshot.json +1643 -549
  211. package/package.json +24 -6
  212. package/tokens/brands/default.json +5 -0
  213. package/tokens/brands/practicepro.json +27 -0
  214. package/tokens/lib.cjs +128 -0
  215. package/tokens/tokens.json +133 -0
@@ -0,0 +1,494 @@
1
+ 'use client';
2
+ import { objectWithoutProperties as _objectWithoutProperties, slicedToArray as _slicedToArray, objectSpread2 as _objectSpread2 } from '../../_virtual/_rollupPluginBabelHelpers.js';
3
+ import { jsxs, jsx } from 'react/jsx-runtime';
4
+ import { forwardRef, useId, useMemo, useState, useRef, useEffect, useLayoutEffect } from 'react';
5
+ import ReactDOM from 'react-dom';
6
+ import { startOfDay, startOfMonth, startOfWeek, endOfMonth, endOfWeek, eachDayOfInterval, subMonths, format, addMonths, isSameDay, isSameMonth, isToday, parseISO, isAfter, isBefore } from 'date-fns';
7
+ import { cn } from '../../utils/cn.js';
8
+ import { ChevronLeft, ChevronRight, Calendar, X } from 'lucide-react';
9
+
10
+ var _excluded = ["value", "onChange", "placeholder", "label", "error", "helperText", "disabled", "required", "minDate", "maxDate", "className", "weekStartsOn", "portal", "displayFormat", "locale", "showClear", "closeOnSelect"];
11
+ /*
12
+ Single-calendar DateRangePicker with hover-preview and space-aware popup positioning
13
+ - Enforces mandatory range selection until both start and end are chosen.
14
+ - Popup flips above/below and clamps horizontally to avoid clipping.
15
+ - Popup won't close while only a start date is selected (Clear cancels in-progress selection).
16
+ */
17
+ var normalizeDate = function normalizeDate(d) {
18
+ if (!d) return null;
19
+ if (typeof d === "string") {
20
+ try {
21
+ return parseISO(d);
22
+ } catch (e) {
23
+ return null;
24
+ }
25
+ }
26
+ return d instanceof Date ? d : null;
27
+ };
28
+ var DateRangePicker = /*#__PURE__*/forwardRef(function DateRangePicker(_ref, ref) {
29
+ var value = _ref.value,
30
+ onChange = _ref.onChange,
31
+ _ref$placeholder = _ref.placeholder,
32
+ placeholder = _ref$placeholder === void 0 ? "Select date range..." : _ref$placeholder,
33
+ label = _ref.label,
34
+ error = _ref.error,
35
+ helperText = _ref.helperText,
36
+ disabled = _ref.disabled,
37
+ required = _ref.required,
38
+ minDate = _ref.minDate,
39
+ maxDate = _ref.maxDate,
40
+ className = _ref.className,
41
+ _ref$weekStartsOn = _ref.weekStartsOn,
42
+ weekStartsOn = _ref$weekStartsOn === void 0 ? 0 : _ref$weekStartsOn,
43
+ _ref$portal = _ref.portal,
44
+ portal = _ref$portal === void 0 ? false : _ref$portal,
45
+ _ref$displayFormat = _ref.displayFormat,
46
+ displayFormat = _ref$displayFormat === void 0 ? "MMM dd, yyyy" : _ref$displayFormat,
47
+ _ref$locale = _ref.locale,
48
+ locale = _ref$locale === void 0 ? undefined : _ref$locale,
49
+ _ref$showClear = _ref.showClear,
50
+ showClear = _ref$showClear === void 0 ? true : _ref$showClear,
51
+ _ref$closeOnSelect = _ref.closeOnSelect,
52
+ closeOnSelect = _ref$closeOnSelect === void 0 ? true : _ref$closeOnSelect,
53
+ props = _objectWithoutProperties(_ref, _excluded);
54
+ // Stable ids wiring label / helper / error to the trigger button.
55
+ // A consumer `id` (spread onto the trigger via ...props) wins.
56
+ var reactId = useId();
57
+ var fieldId = props.id || "daterangepicker-".concat(reactId);
58
+ var labelId = "".concat(fieldId, "-label");
59
+ var valueId = "".concat(fieldId, "-value");
60
+ var errorId = "".concat(fieldId, "-error");
61
+ var helperId = "".concat(fieldId, "-helper");
62
+ var start = useMemo(function () {
63
+ return normalizeDate(value === null || value === void 0 ? void 0 : value.start);
64
+ }, [value]);
65
+ var end = useMemo(function () {
66
+ return normalizeDate(value === null || value === void 0 ? void 0 : value.end);
67
+ }, [value]);
68
+ var _useState = useState(false),
69
+ _useState2 = _slicedToArray(_useState, 2),
70
+ isOpen = _useState2[0],
71
+ setIsOpen = _useState2[1];
72
+ var _useState3 = useState(start || new Date()),
73
+ _useState4 = _slicedToArray(_useState3, 2),
74
+ currentMonth = _useState4[0],
75
+ setCurrentMonth = _useState4[1];
76
+ // local selection state so user can pick start then end before commit
77
+ var _useState5 = useState(start),
78
+ _useState6 = _slicedToArray(_useState5, 2),
79
+ localStart = _useState6[0],
80
+ setLocalStart = _useState6[1];
81
+ var _useState7 = useState(end),
82
+ _useState8 = _slicedToArray(_useState7, 2),
83
+ localEnd = _useState8[0],
84
+ setLocalEnd = _useState8[1];
85
+ // hover preview while picking second date
86
+ var _useState9 = useState(null),
87
+ _useState0 = _slicedToArray(_useState9, 2),
88
+ hoverDate = _useState0[0],
89
+ setHoverDate = _useState0[1];
90
+ // Re-sync local selection when the external value changes
91
+ // (adjust-during-render pattern).
92
+ var _useState1 = useState(value),
93
+ _useState10 = _slicedToArray(_useState1, 2),
94
+ prevValue = _useState10[0],
95
+ setPrevValue = _useState10[1];
96
+ if (value !== prevValue) {
97
+ setPrevValue(value);
98
+ setLocalStart(start);
99
+ setLocalEnd(end);
100
+ if (start) setCurrentMonth(start);
101
+ }
102
+ var datePickerRef = useRef(null);
103
+ var popupRef = useRef(null);
104
+ var _useState11 = useState({}),
105
+ _useState12 = _slicedToArray(_useState11, 2),
106
+ popupStyle = _useState12[0],
107
+ setPopupStyle = _useState12[1];
108
+ // Outside click: only close if there is NOT an in-progress single-date selection
109
+ useEffect(function () {
110
+ var handleClickOutside = function handleClickOutside(e) {
111
+ if (datePickerRef.current && !datePickerRef.current.contains(e.target) && (!popupRef.current || !popupRef.current.contains(e.target))) {
112
+ // If user has selected a start but no end, prevent closing to force range selection
113
+ if (localStart && !localEnd) {
114
+ return;
115
+ }
116
+ setIsOpen(false);
117
+ }
118
+ };
119
+ document.addEventListener("mousedown", handleClickOutside);
120
+ return function () {
121
+ return document.removeEventListener("mousedown", handleClickOutside);
122
+ };
123
+ }, [localStart, localEnd]);
124
+ // Keyboard handling: Escape should not close when only start selected
125
+ useEffect(function () {
126
+ var handleKey = function handleKey(e) {
127
+ if (!isOpen) return;
128
+ if (e.key === "Escape") {
129
+ if (localStart && !localEnd) return; // ignore escape while selecting range
130
+ setIsOpen(false);
131
+ }
132
+ };
133
+ document.addEventListener("keydown", handleKey);
134
+ return function () {
135
+ return document.removeEventListener("keydown", handleKey);
136
+ };
137
+ }, [isOpen, localStart, localEnd]);
138
+ var min = minDate ? startOfDay(normalizeDate(minDate)) : null;
139
+ var max = maxDate ? startOfDay(normalizeDate(maxDate)) : null;
140
+ var isDisabled = function isDisabled(d) {
141
+ var dd = startOfDay(d);
142
+ if (min && isBefore(dd, min)) return true;
143
+ if (max && isAfter(dd, max)) return true;
144
+ return false;
145
+ };
146
+ var monthStart = startOfMonth(currentMonth);
147
+ var monthGridStart = startOfWeek(monthStart, {
148
+ weekStartsOn: weekStartsOn
149
+ });
150
+ var monthEnd = endOfMonth(currentMonth);
151
+ var monthGridEnd = endOfWeek(monthEnd, {
152
+ weekStartsOn: weekStartsOn
153
+ });
154
+ var days = useMemo(function () {
155
+ return eachDayOfInterval({
156
+ start: monthGridStart,
157
+ end: monthGridEnd
158
+ });
159
+ }, [monthGridStart, monthGridEnd]);
160
+ // Compute whether a day is considered 'in range' for highlighting.
161
+ // If user has selected start and is hovering (no end yet) we preview using hoverDate.
162
+ var dayInRange = function dayInRange(d) {
163
+ var s = localStart ? startOfDay(localStart) : null;
164
+ var e = localEnd ? startOfDay(localEnd) : null;
165
+ var hover = hoverDate ? startOfDay(hoverDate) : null;
166
+ if (s && e) {
167
+ return isSameDay(d, s) || isSameDay(d, e) || isAfter(startOfDay(d), s) && isBefore(startOfDay(d), e);
168
+ }
169
+ if (s && !e && hover) {
170
+ var a = isBefore(hover, s) ? hover : s;
171
+ var b = isBefore(hover, s) ? s : hover;
172
+ return isSameDay(d, a) || isSameDay(d, b) || isAfter(startOfDay(d), a) && isBefore(startOfDay(d), b);
173
+ }
174
+ return false;
175
+ };
176
+ var handleDayClick = function handleDayClick(d) {
177
+ if (isDisabled(d)) return;
178
+ // No start selected -> pick start
179
+ if (!localStart && !localEnd) {
180
+ setLocalStart(d);
181
+ setLocalEnd(null);
182
+ setHoverDate(null);
183
+ setCurrentMonth(d);
184
+ return;
185
+ }
186
+ // Have start but no end -> pick end (swap if before)
187
+ if (localStart && !localEnd) {
188
+ if (isBefore(d, localStart)) {
189
+ var newStart = d;
190
+ var newEnd = localStart;
191
+ setLocalStart(newStart);
192
+ setLocalEnd(newEnd);
193
+ // commit only when both present
194
+ onChange === null || onChange === void 0 || onChange({
195
+ start: newStart,
196
+ end: newEnd
197
+ });
198
+ } else {
199
+ setLocalEnd(d);
200
+ onChange === null || onChange === void 0 || onChange({
201
+ start: localStart,
202
+ end: d
203
+ });
204
+ }
205
+ setHoverDate(null);
206
+ // Close the popup only after the second date is selected
207
+ if (closeOnSelect) setIsOpen(false);
208
+ return;
209
+ }
210
+ // Both present -> start a new selection with clicked date as start
211
+ if (localStart && localEnd) {
212
+ setLocalStart(d);
213
+ setLocalEnd(null);
214
+ setHoverDate(null);
215
+ setCurrentMonth(d);
216
+ return;
217
+ }
218
+ };
219
+ var clearSelection = function clearSelection() {
220
+ setLocalStart(null);
221
+ setLocalEnd(null);
222
+ setHoverDate(null);
223
+ onChange === null || onChange === void 0 || onChange(null);
224
+ };
225
+ // A disabled picker cannot be cleared (the old nested button could not be
226
+ // clicked inside a disabled trigger either).
227
+ var showClearButton = Boolean(showClear && !disabled && (localStart || localEnd));
228
+ // Display placeholder until both start and end are selected (mandatory range)
229
+ var formatDisplay = function formatDisplay() {
230
+ if (!localStart || !localEnd) return placeholder;
231
+ return "".concat(format(localStart, displayFormat, {
232
+ locale: locale
233
+ }), " \u2014 ").concat(format(localEnd, displayFormat, {
234
+ locale: locale
235
+ }));
236
+ };
237
+ var header = jsxs("div", {
238
+ className: "mb-2 flex items-center justify-between",
239
+ children: [jsx("button", {
240
+ type: "button",
241
+ className: "rounded-md p-1 hover:bg-gray-100",
242
+ onClick: function onClick() {
243
+ return setCurrentMonth(function (m) {
244
+ return subMonths(m, 1);
245
+ });
246
+ },
247
+ "aria-label": "Previous month",
248
+ children: jsx(ChevronLeft, {
249
+ className: "h-4 w-4"
250
+ })
251
+ }), jsx("h4", {
252
+ className: "text-sm font-medium",
253
+ children: format(currentMonth, "MMMM yyyy")
254
+ }), jsx("button", {
255
+ type: "button",
256
+ className: "rounded-md p-1 hover:bg-gray-100",
257
+ onClick: function onClick() {
258
+ return setCurrentMonth(function (m) {
259
+ return addMonths(m, 1);
260
+ });
261
+ },
262
+ "aria-label": "Next month",
263
+ children: jsx(ChevronRight, {
264
+ className: "h-4 w-4"
265
+ })
266
+ })]
267
+ });
268
+ // compute popup position so it's aware of viewport edges (top/bottom/left/right)
269
+ var computePopupPosition = function computePopupPosition() {
270
+ var margin = 8;
271
+ var trigger = datePickerRef.current;
272
+ var popup = popupRef.current;
273
+ if (!trigger || !popup) return;
274
+ var triggerRect = trigger.getBoundingClientRect();
275
+ // measure popup by temporarily making it visible if hidden
276
+ var popupRect = popup.getBoundingClientRect();
277
+ // If the popup is not yet fully measured (width/height 0), try to read offsetWidth/Height
278
+ if (popupRect.width === 0 || popupRect.height === 0) {
279
+ popupRect = {
280
+ width: popup.offsetWidth || 320,
281
+ height: popup.offsetHeight || 300
282
+ };
283
+ }
284
+ // Decide vertical placement
285
+ var spaceBelow = window.innerHeight - triggerRect.bottom;
286
+ var spaceAbove = triggerRect.top;
287
+ var placeBelow = spaceBelow >= popupRect.height + margin;
288
+ if (!placeBelow && spaceAbove >= popupRect.height + margin) placeBelow = false;
289
+ // If neither side has full space, pick the side with more space
290
+ if (!placeBelow && spaceBelow < popupRect.height + margin && spaceAbove < popupRect.height + margin) {
291
+ placeBelow = spaceBelow >= spaceAbove;
292
+ }
293
+ // compute top coordinate (fixed positioning)
294
+ var top;
295
+ if (placeBelow) {
296
+ top = Math.min(window.innerHeight - margin - popupRect.height, triggerRect.bottom + margin);
297
+ } else {
298
+ top = Math.max(margin, triggerRect.top - popupRect.height - margin);
299
+ }
300
+ // Decide horizontal placement (try align left to trigger)
301
+ var left = triggerRect.left;
302
+ // If overflowing right, shift left
303
+ if (left + popupRect.width > window.innerWidth - margin) {
304
+ left = Math.max(margin, window.innerWidth - margin - popupRect.width);
305
+ }
306
+ // If overflowing left, clamp
307
+ if (left < margin) left = margin;
308
+ var transformOrigin = placeBelow ? "top left" : "bottom left";
309
+ setPopupStyle({
310
+ position: "fixed",
311
+ top: "".concat(Math.round(top), "px"),
312
+ left: "".concat(Math.round(left), "px"),
313
+ zIndex: 9999,
314
+ transformOrigin: transformOrigin
315
+ });
316
+ };
317
+ // recompute on open, on resize/scroll, and when popup content changes (currentMonth)
318
+ useLayoutEffect(function () {
319
+ if (!isOpen) return;
320
+ // compute on next frame to ensure popup ref exists in DOM (especially with portal)
321
+ // Measuring the DOM and positioning the popup before paint is the
322
+ // intended use of a layout effect, so the setState inside is deliberate.
323
+ // eslint-disable-next-line react-hooks/set-state-in-effect
324
+ computePopupPosition();
325
+ var handleResize = function handleResize() {
326
+ return computePopupPosition();
327
+ };
328
+ window.addEventListener("resize", handleResize);
329
+ window.addEventListener("scroll", handleResize, true); // capture scrolling in ancestors
330
+ return function () {
331
+ window.removeEventListener("resize", handleResize);
332
+ window.removeEventListener("scroll", handleResize, true);
333
+ };
334
+ }, [isOpen, currentMonth]);
335
+ var calendarPopup = jsxs("div", {
336
+ ref: popupRef,
337
+ className: "w-80 rounded-md border border-gray-300 bg-white p-4 shadow-lg",
338
+ role: "dialog",
339
+ "aria-modal": "false",
340
+ style: popupStyle,
341
+ children: [header, jsx("div", {
342
+ className: "mb-2 grid grid-cols-7 gap-1",
343
+ children: Array.from({
344
+ length: 7
345
+ }).map(function (_, i) {
346
+ var dayIndex = (i + weekStartsOn) % 7;
347
+ var label = ["Su", "Mo", "Tu", "We", "Th", "Fr", "Sa"][dayIndex];
348
+ return jsx("div", {
349
+ className: "p-2 text-center text-xs font-medium text-primary-500",
350
+ children: label
351
+ }, label);
352
+ })
353
+ }), jsx("div", {
354
+ className: "grid grid-cols-7 gap-1",
355
+ role: "grid",
356
+ children: days.map(function (d) {
357
+ var isSelStart = localStart && isSameDay(d, localStart);
358
+ var isSelEnd = localEnd && isSameDay(d, localEnd);
359
+ var inRange = dayInRange(d);
360
+ var isCurrentMonth = isSameMonth(d, currentMonth);
361
+ var today = isToday(d);
362
+ var disabled = isDisabled(d);
363
+ return jsx("button", {
364
+ type: "button",
365
+ onClick: function onClick() {
366
+ return handleDayClick(d);
367
+ },
368
+ onMouseEnter: function onMouseEnter() {
369
+ // only set hover when picking end (start selected, end not)
370
+ if (localStart && !localEnd) setHoverDate(d);
371
+ },
372
+ onMouseLeave: function onMouseLeave() {
373
+ if (localStart && !localEnd) setHoverDate(null);
374
+ },
375
+ disabled: disabled,
376
+ className: cn("h-8 w-8 rounded-md text-sm transition-colors focus:outline-2 focus:outline-offset-1", {
377
+ "text-primary-400": !isCurrentMonth,
378
+ "bg-primary-600 text-white": isSelStart || isSelEnd,
379
+ "bg-primary-100 text-primary-600": today && !(isSelStart || isSelEnd),
380
+ "bg-primary-200": inRange && !(isSelStart || isSelEnd),
381
+ "hover:bg-gray-100": isCurrentMonth && !(isSelStart || isSelEnd) && !disabled,
382
+ "cursor-not-allowed opacity-50": disabled
383
+ }),
384
+ children: format(d, "d")
385
+ }, d.toISOString());
386
+ })
387
+ }), jsxs("div", {
388
+ className: "mt-3 flex items-center justify-between",
389
+ children: [jsxs("div", {
390
+ className: "flex gap-2",
391
+ children: [showClear && jsx("button", {
392
+ type: "button",
393
+ className: "rounded-md px-2 py-1 text-xs hover:bg-gray-100",
394
+ onClick: clearSelection,
395
+ children: "Clear"
396
+ }), jsx("button", {
397
+ type: "button",
398
+ className: "rounded-md px-2 py-1 text-xs hover:bg-gray-100",
399
+ onClick: function onClick() {
400
+ var t = new Date();
401
+ setLocalStart(t);
402
+ setLocalEnd(t);
403
+ onChange === null || onChange === void 0 || onChange({
404
+ start: t,
405
+ end: t
406
+ });
407
+ if (closeOnSelect) setIsOpen(false);
408
+ },
409
+ children: "Today"
410
+ })]
411
+ }), jsx("div", {
412
+ className: "flex items-center gap-2",
413
+ children: jsx("div", {
414
+ className: "text-xs text-primary-500",
415
+ children: !localStart || !localEnd ? placeholder : "".concat(format(localStart, displayFormat, {
416
+ locale: locale
417
+ }), " \u2014 ").concat(format(localEnd, displayFormat, {
418
+ locale: locale
419
+ }))
420
+ })
421
+ })]
422
+ })]
423
+ });
424
+ return jsxs("div", {
425
+ ref: ref,
426
+ className: "w-full",
427
+ children: [label && jsxs("label", {
428
+ id: labelId,
429
+ htmlFor: fieldId,
430
+ className: "block text-sm font-medium mb-1",
431
+ children: [label, required && jsx("span", {
432
+ className: "text-error-500 ml-1",
433
+ children: "*"
434
+ })]
435
+ }), jsxs("div", {
436
+ ref: datePickerRef,
437
+ className: "relative",
438
+ children: [jsxs("button", _objectSpread2(_objectSpread2({
439
+ type: "button",
440
+ id: fieldId,
441
+ "aria-labelledby": label ? "".concat(labelId, " ").concat(valueId) : valueId,
442
+ "aria-describedby": error ? errorId : helperText ? helperId : undefined,
443
+ "aria-invalid": error ? true : undefined,
444
+ className: cn("flex w-full items-center justify-between rounded-md border border-gray-300 bg-white px-3 py-2 text-sm focus:outline-none focus:ring-2 focus:ring-primary-500 focus:border-transparent disabled:cursor-not-allowed disabled:opacity-50", {
445
+ "border-error-500 focus:ring-error-500": error,
446
+ "text-primary-500": !localStart || !localEnd,
447
+ "pr-16": showClearButton
448
+ }, className),
449
+ onClick: function onClick() {
450
+ // Prevent closing the popup when a start is selected but end isn't — force user to finish range
451
+ if (isOpen && localStart && !localEnd) return;
452
+ setIsOpen(function (v) {
453
+ return !v;
454
+ });
455
+ },
456
+ disabled: disabled,
457
+ "aria-haspopup": "dialog",
458
+ "aria-expanded": isOpen
459
+ }, props), {}, {
460
+ children: [jsx("span", {
461
+ className: "truncate",
462
+ id: valueId,
463
+ children: formatDisplay()
464
+ }), jsx(Calendar, {
465
+ className: "h-4 w-4 shrink-0 text-primary-400",
466
+ "aria-hidden": "true"
467
+ })]
468
+ })), showClearButton && jsx("button", {
469
+ type: "button",
470
+ onClick: function onClick(e) {
471
+ e.stopPropagation();
472
+ clearSelection();
473
+ },
474
+ "aria-label": "Clear range",
475
+ className: "absolute right-8 top-1/2 -translate-y-1/2 rounded p-1 focus:outline-none focus:ring-2 focus:ring-primary-500",
476
+ children: jsx(X, {
477
+ className: "h-4 w-4 text-primary-400",
478
+ "aria-hidden": "true"
479
+ })
480
+ }), isOpen && (portal ? /*#__PURE__*/ReactDOM.createPortal(calendarPopup, document.body) : calendarPopup)]
481
+ }), error ? jsx("p", {
482
+ id: errorId,
483
+ className: "mt-1 text-sm text-error-500",
484
+ children: error
485
+ }) : helperText ? jsx("p", {
486
+ id: helperId,
487
+ className: "mt-1 text-sm text-muted",
488
+ children: helperText
489
+ }) : null]
490
+ });
491
+ });
492
+ DateRangePicker.displayName = "DateRangePicker";
493
+
494
+ export { DateRangePicker as default };