@altruist03/ui 0.1.0 → 0.2.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 (207) hide show
  1. package/assets/images/noData.webp +0 -0
  2. package/dist/index.js +15237 -650
  3. package/dist/index.js.map +1 -1
  4. package/dist/noData-XPRHKMFR.webp +0 -0
  5. package/package.json +21 -7
  6. package/src/ThemeProvider/ThemeContext.tsx +75 -0
  7. package/src/ThemeProvider/index.ts +7 -0
  8. package/src/assets/images/noData.webp +0 -0
  9. package/src/assets.d.ts +10 -0
  10. package/src/components/Accordion/Accordion.stories.tsx +64 -0
  11. package/src/components/Accordion/Accordion.tsx +219 -0
  12. package/src/components/Accordion/Accordion.types.ts +16 -0
  13. package/src/components/Avatar/Avatar.stories.tsx +304 -0
  14. package/src/components/Avatar/Avatar.tsx +211 -0
  15. package/src/components/Avatar/Avatar.types.ts +33 -0
  16. package/src/components/Badge/Badge.stories.tsx +143 -0
  17. package/src/components/Badge/Badge.tsx +138 -0
  18. package/src/components/Badge/Badge.types.ts +24 -0
  19. package/src/components/Breadcrumb/Breadcrumb.stories.tsx +114 -0
  20. package/src/components/Breadcrumb/Breadcrumb.tsx +120 -0
  21. package/src/components/Breadcrumb/Breadcrumb.types.ts +22 -0
  22. package/src/components/Button/Button.stories.tsx +457 -0
  23. package/src/components/Button/Button.tsx +202 -0
  24. package/src/components/Button/Button.types.ts +18 -0
  25. package/src/components/Checkbox/Checkbox.stories.tsx +254 -0
  26. package/src/components/Checkbox/Checkbox.tsx +352 -0
  27. package/src/components/Checkbox/Checkbox.types.ts +123 -0
  28. package/src/components/CloseButton/CloseButton.stories.tsx +100 -0
  29. package/src/components/CloseButton/CloseButton.tsx +117 -0
  30. package/src/components/CloseButton/CloseButton.types.ts +15 -0
  31. package/src/components/CollapsibleSearch/CollapsibleSearch.tsx +120 -0
  32. package/src/components/DataTable/DataTable.stories.tsx +233 -0
  33. package/src/components/DataTable/DataTable.tsx +968 -0
  34. package/src/components/DataTable/DataTable.types.ts +49 -0
  35. package/src/components/DatePicker/DatePicker.stories.tsx +341 -0
  36. package/src/components/DatePicker/DatePicker.tsx +669 -0
  37. package/src/components/DatePicker/DatePicker.types.ts +105 -0
  38. package/src/components/DatePicker/DateTimePicker.tsx +35 -0
  39. package/src/components/DateRangePicker/DateRangePicker.tsx +179 -0
  40. package/src/components/DateRangePicker/DateRangePicker.types.ts +34 -0
  41. package/src/components/Drawer/Drawer.stories.tsx +243 -0
  42. package/src/components/Drawer/Drawer.tsx +196 -0
  43. package/src/components/Drawer/Drawer.types.ts +60 -0
  44. package/src/components/FileUpload/FileUpload.stories.tsx +162 -0
  45. package/src/components/FileUpload/FileUpload.tsx +216 -0
  46. package/src/components/FileUpload/FileUpload.types.ts +80 -0
  47. package/src/components/FilterPopover/FilterPopover.stories.tsx +173 -0
  48. package/src/components/FilterPopover/FilterPopover.tsx +310 -0
  49. package/src/components/FilterPopover/FilterPopover.types.ts +26 -0
  50. package/src/components/ImageViewer/ImageViewerToolbar.jsx +81 -0
  51. package/src/components/ImageViewer/useImagePanRotate.js +102 -0
  52. package/src/components/IpWhitelistField/IpWhitelistField.stories.tsx +138 -0
  53. package/src/components/IpWhitelistField/IpWhitelistField.tsx +164 -0
  54. package/src/components/IpWhitelistField/IpWhitelistField.types.ts +42 -0
  55. package/src/components/Loader/Loader.stories.tsx +95 -0
  56. package/src/components/Loader/Loader.tsx +132 -0
  57. package/src/components/Loader/Loader.types.ts +6 -0
  58. package/src/components/Menu/Menu.tsx +143 -0
  59. package/src/components/Menu/Menu.types.ts +28 -0
  60. package/src/components/MenuButton/MenuButton.stories.tsx +140 -0
  61. package/src/components/MenuButton/MenuButton.tsx +45 -0
  62. package/src/components/MenuButton/MenuButton.types.ts +21 -0
  63. package/src/components/Modal/Modal.stories.tsx +266 -0
  64. package/src/components/Modal/Modal.tsx +399 -0
  65. package/src/components/Modal/index.ts +2 -0
  66. package/src/components/Modal/types.ts +126 -0
  67. package/src/components/Pagination/Pagination.stories.tsx +167 -0
  68. package/src/components/Pagination/Pagination.tsx +266 -0
  69. package/src/components/Pagination/Pagination.types.ts +20 -0
  70. package/src/components/PeriodSelector/PeriodSelector.stories.tsx +50 -0
  71. package/src/components/PeriodSelector/PeriodSelector.tsx +47 -0
  72. package/src/components/PeriodSelector/PeriodSelector.types.ts +10 -0
  73. package/src/components/PhoneField/PhoneField.stories.tsx +141 -0
  74. package/src/components/PhoneField/PhoneField.tsx +284 -0
  75. package/src/components/PhoneField/PhoneField.types.ts +89 -0
  76. package/src/components/PhoneField/countryCodes.ts +236 -0
  77. package/src/components/Progress/Progress.stories.tsx +83 -0
  78. package/src/components/Progress/Progress.tsx +91 -0
  79. package/src/components/Progress/Progress.types.ts +22 -0
  80. package/src/components/RadioButton/RadioButton.stories.tsx +332 -0
  81. package/src/components/RadioButton/RadioButton.tsx +427 -0
  82. package/src/components/RadioButton/RadioButton.types.ts +117 -0
  83. package/src/components/ReportCard/ReportCard.tsx +183 -0
  84. package/src/components/ReportCard/ReportCard.types.ts +17 -0
  85. package/src/components/SectionIcon/SectionIcon.tsx +87 -0
  86. package/src/components/SectionIcon/SectionIcon.types.ts +23 -0
  87. package/src/components/SegmentedInputField/SegmentedInputField.stories.tsx +165 -0
  88. package/src/components/SegmentedInputField/SegmentedInputField.tsx +174 -0
  89. package/src/components/SegmentedInputField/SegmentedInputField.types.ts +51 -0
  90. package/src/components/SelectScroll/SelectScroll.stories.tsx +336 -0
  91. package/src/components/SelectScroll/SelectScroll.tsx +1119 -0
  92. package/src/components/SelectScroll/SelectScroll.types.ts +60 -0
  93. package/src/components/Selector/Selector.stories.tsx +382 -0
  94. package/src/components/Selector/Selector.tsx +1310 -0
  95. package/src/components/Selector/Selector.types.ts +71 -0
  96. package/src/components/Skeleton/Skeleton.stories.tsx +133 -0
  97. package/src/components/Skeleton/Skeleton.tsx +20 -0
  98. package/src/components/Skeleton/Skeleton.types.ts +3 -0
  99. package/src/components/Spinner/Spinner.stories.tsx +84 -0
  100. package/src/components/Spinner/Spinner.tsx +79 -0
  101. package/src/components/Spinner/Spinner.types.ts +8 -0
  102. package/src/components/StatCard/StatCard.stories.tsx +125 -0
  103. package/src/components/StatCard/StatCard.tsx +167 -0
  104. package/src/components/StatCard/StatCard.types.ts +20 -0
  105. package/src/components/StatusBadge/StatusBadge.stories.tsx +179 -0
  106. package/src/components/StatusBadge/StatusBadge.tsx +74 -0
  107. package/src/components/StatusBadge/StatusBadge.types.ts +23 -0
  108. package/src/components/Stepper/Stepper.stories.tsx +85 -0
  109. package/src/components/Stepper/Stepper.tsx +138 -0
  110. package/src/components/Stepper/Stepper.types.ts +16 -0
  111. package/src/components/SubTabs/SubTabs.stories.tsx +128 -0
  112. package/src/components/SubTabs/SubTabs.tsx +279 -0
  113. package/src/components/SubTabs/SubTabs.types.ts +44 -0
  114. package/src/components/SummaryCard/SummaryCard.stories.tsx +100 -0
  115. package/src/components/SummaryCard/SummaryCard.tsx +141 -0
  116. package/src/components/SummaryCard/SummaryCard.types.ts +22 -0
  117. package/src/components/Switch/Switch.stories.tsx +59 -0
  118. package/src/components/Switch/Switch.tsx +217 -0
  119. package/src/components/Switch/Switch.types.ts +22 -0
  120. package/src/components/Tabs/Tabs.stories.tsx +178 -0
  121. package/src/components/Tabs/Tabs.tsx +197 -0
  122. package/src/components/Tabs/Tabs.types.ts +34 -0
  123. package/src/components/TextField/TextField.stories.tsx +249 -0
  124. package/src/components/TextField/TextField.tsx +462 -0
  125. package/src/components/TextField/TextField.types.ts +141 -0
  126. package/src/components/Toast/Toast.stories.tsx +237 -0
  127. package/src/components/Toast/Toast.tsx +246 -0
  128. package/src/components/Toast/Toast.types.ts +31 -0
  129. package/src/components/ToggleButton/ToggleButton.stories.tsx +105 -0
  130. package/src/components/ToggleButton/ToggleButton.tsx +267 -0
  131. package/src/components/ToggleButton/ToggleButton.types.ts +19 -0
  132. package/src/components/UploadDropzone/UploadDropzone.jsx +78 -0
  133. package/src/components/UploadDropzone2/UploadDropzone.stories.tsx +94 -0
  134. package/src/components/UploadDropzone2/UploadDropzone.tsx +222 -0
  135. package/src/components/UploadDropzone2/UploadDropzone.types.ts +54 -0
  136. package/src/components/ViewEditToggle/ViewEditToggle.stories.tsx +40 -0
  137. package/src/components/ViewEditToggle/ViewEditToggle.tsx +239 -0
  138. package/src/components/ViewEditToggle/ViewEditToggle.types.ts +27 -0
  139. package/src/components/index.ts +157 -0
  140. package/src/icons/ReportDollarIcon.tsx +71 -0
  141. package/src/icons/index.ts +27 -0
  142. package/src/index.ts +214 -0
  143. package/src/storybook/preview.tsx +64 -0
  144. package/src/theme/index.ts +4 -0
  145. package/src/theme/types.d.ts +1 -0
  146. package/src/themes/cpc.ts +4 -0
  147. package/src/themes/firebird.ts +4 -0
  148. package/src/tokens/brands/cpc/themes/dark.json +82 -0
  149. package/src/tokens/brands/cpc/themes/light.json +1 -0
  150. package/src/tokens/brands/default/brand.json +20 -0
  151. package/src/tokens/brands/firebird/themes/dark.json +28 -0
  152. package/src/tokens/brands/firebird/themes/light.json +20 -0
  153. package/src/tokens/button.ts +24 -0
  154. package/src/tokens/components/accordion.json +52 -0
  155. package/src/tokens/components/avatar.json +40 -0
  156. package/src/tokens/components/badge.json +41 -0
  157. package/src/tokens/components/breadcrumb.json +13 -0
  158. package/src/tokens/components/button.json +67 -0
  159. package/src/tokens/components/card.json +39 -0
  160. package/src/tokens/components/checkbox.json +64 -0
  161. package/src/tokens/components/closebutton.json +55 -0
  162. package/src/tokens/components/datatable.json +40 -0
  163. package/src/tokens/components/datepicker.json +99 -0
  164. package/src/tokens/components/drawer.json +23 -0
  165. package/src/tokens/components/field.json +29 -0
  166. package/src/tokens/components/fileupload.json +57 -0
  167. package/src/tokens/components/filterpopover.json +29 -0
  168. package/src/tokens/components/input.json +31 -0
  169. package/src/tokens/components/ipwhitelistfield.json +16 -0
  170. package/src/tokens/components/loader.json +15 -0
  171. package/src/tokens/components/menu.json +21 -0
  172. package/src/tokens/components/modal.json +25 -0
  173. package/src/tokens/components/pagination.json +45 -0
  174. package/src/tokens/components/phonefield.json +41 -0
  175. package/src/tokens/components/progress.json +12 -0
  176. package/src/tokens/components/radio.json +87 -0
  177. package/src/tokens/components/reportcard.json +17 -0
  178. package/src/tokens/components/selector.json +87 -0
  179. package/src/tokens/components/selectscroll.json +65 -0
  180. package/src/tokens/components/skeleton.json +6 -0
  181. package/src/tokens/components/stepper.json +38 -0
  182. package/src/tokens/components/subtabs.json +45 -0
  183. package/src/tokens/components/switch.json +55 -0
  184. package/src/tokens/components/tabs.json +38 -0
  185. package/src/tokens/components/textfield.json +59 -0
  186. package/src/tokens/components/togglebutton.json +48 -0
  187. package/src/tokens/components/uploaddropzone.json +61 -0
  188. package/src/tokens/drawer.ts +37 -0
  189. package/src/tokens/foundation/breakpoints.json +10 -0
  190. package/src/tokens/foundation/colors.json +122 -0
  191. package/src/tokens/foundation/opacity.json +16 -0
  192. package/src/tokens/foundation/radius.json +14 -0
  193. package/src/tokens/foundation/size.json +38 -0
  194. package/src/tokens/foundation/spacing.json +19 -0
  195. package/src/tokens/foundation/typography.json +43 -0
  196. package/src/tokens/index.ts +3665 -0
  197. package/src/tokens/modal.ts +42 -0
  198. package/src/tokens/semantic/color.json +148 -0
  199. package/src/tokens/semantic/field.json +28 -0
  200. package/src/tokens/semantic/opacity.json +5 -0
  201. package/src/tokens/semantic/size.json +722 -0
  202. package/src/tokens/semantic/spacing.json +27 -0
  203. package/src/tokens/semantic/typography.json +87 -0
  204. package/src/utils/scrollLock.ts +44 -0
  205. package/dist/index.css +0 -514
  206. package/dist/index.css.map +0 -1
  207. package/dist/index.d.ts +0 -289
@@ -0,0 +1,669 @@
1
+ import React, { forwardRef, useState, useRef, useEffect } from 'react';
2
+ import ReactDatePicker from 'react-datepicker';
3
+ import type { DatePickerProps } from 'react-datepicker';
4
+ import CalendarMonthIcon from '@mui/icons-material/CalendarMonth';
5
+ import AccessTimeOutlinedIcon from '@mui/icons-material/AccessTimeOutlined';
6
+ import { getDatePickerTokensFromResolved, getTextFieldTokensFromResolved } from '../../tokens';
7
+ import { useTokens } from '../../ThemeProvider/ThemeContext';
8
+ import { TextField } from '../TextField/TextField';
9
+ import 'react-datepicker/dist/react-datepicker.css';
10
+ // import "firebird-icon-lib/dist/style.css";
11
+
12
+ export type { DatePickerProps };
13
+
14
+ /** Props that react-datepicker passes to customInput (value, onClick, etc.) plus our own */
15
+ export interface ExampleCustomInputProps extends Omit<
16
+ React.InputHTMLAttributes<HTMLInputElement>,
17
+ 'size'
18
+ > {
19
+ error?: boolean;
20
+ size?: 'sm' | 'md' | 'lg';
21
+ className?: string;
22
+ /** Optional icon name for the trailing adornment (defaults to calendar) */
23
+ iconName?: string;
24
+ /** When provided, enables click-to-toggle: skip opening on focus when focus came from click */
25
+ mouseDownOnFieldRef?: React.MutableRefObject<boolean>;
26
+ /** When provided, enables click-to-toggle behavior */
27
+ isOpen?: boolean;
28
+ onToggleOpen?: () => void;
29
+ onToggleClose?: () => void;
30
+ /** Icon at the start (left) of the input, same as TextField startIcon */
31
+ startIcon?: React.ReactNode;
32
+ }
33
+
34
+ /**
35
+ * Custom input for DatePicker that uses TextField styling.
36
+ * Use with: customInput={<ExampleCustomInput className="example-custom-input" />}
37
+ */
38
+ export const ExampleCustomInput = forwardRef<HTMLInputElement, ExampleCustomInputProps>(
39
+ function ExampleCustomInput(props, ref) {
40
+ const {
41
+ value,
42
+ onClick,
43
+ onChange,
44
+ onBlur,
45
+ onFocus,
46
+ placeholder,
47
+ disabled,
48
+ className,
49
+ id,
50
+ error = false,
51
+ size = 'md',
52
+ mouseDownOnFieldRef,
53
+ isOpen,
54
+ onToggleOpen,
55
+ onToggleClose,
56
+ ...rest
57
+ } = props;
58
+
59
+ // const resolvedTokens = useTokens();
60
+ // const dpTokens = getDatePickerTokensFromResolved(resolvedTokens);
61
+
62
+ const valueStr =
63
+ value === undefined || value === null ? ''
64
+ : typeof value === 'string' ? value
65
+ : String(value);
66
+
67
+ const iconFontSize =
68
+ size === 'lg' ? 20
69
+ : size === 'md' ? 18
70
+ : 16;
71
+ const LeadingIcon = props.iconName === 'clock' ? AccessTimeOutlinedIcon : CalendarMonthIcon;
72
+ const inputCursor = disabled ? 'not-allowed' : 'pointer';
73
+ const scopedInputId = typeof id === 'string' ? id : undefined;
74
+ const scopedCursorSelector =
75
+ scopedInputId ?
76
+ `#${CSS.escape(scopedInputId)}.text-field-input`
77
+ : `.${className ?? 'example-custom-input'} input.text-field-input`;
78
+
79
+ return (
80
+ <div
81
+ className={className}
82
+ style={{
83
+ position: 'relative',
84
+ width: '100%',
85
+ display: 'block',
86
+ cursor: inputCursor,
87
+ }}
88
+ >
89
+ <style>{`${scopedCursorSelector} { cursor: ${inputCursor}; }`}</style>
90
+ <TextField
91
+ id={scopedInputId}
92
+ label=""
93
+ helperText=""
94
+ value={valueStr}
95
+ placeholder={placeholder}
96
+ disabled={disabled}
97
+ error={error}
98
+ size={size}
99
+ inputRef={ref}
100
+ startIcon={<LeadingIcon sx={{ fontSize: iconFontSize }} />}
101
+ inputProps={{
102
+ ...rest,
103
+ readOnly: true,
104
+ onMouseDown:
105
+ mouseDownOnFieldRef ?
106
+ () => {
107
+ mouseDownOnFieldRef.current = true;
108
+ }
109
+ : undefined,
110
+ onClick:
111
+ onToggleClose != null ?
112
+ (e: React.MouseEvent<HTMLInputElement>) => {
113
+ if (mouseDownOnFieldRef) mouseDownOnFieldRef.current = false;
114
+ if (disabled) return;
115
+ if ((e.target as HTMLElement).closest('button')) return;
116
+ if (isOpen) {
117
+ onToggleClose();
118
+ } else {
119
+ onToggleOpen?.();
120
+ }
121
+ }
122
+ : (onClick as React.MouseEventHandler<HTMLInputElement>),
123
+ }}
124
+ onChange={onChange as React.ChangeEventHandler<HTMLInputElement | HTMLTextAreaElement>}
125
+ onBlur={onBlur as React.FocusEventHandler<HTMLInputElement | HTMLTextAreaElement>}
126
+ onFocus={(e) => {
127
+ (onFocus as React.FocusEventHandler<HTMLInputElement>)?.(
128
+ e as React.FocusEvent<HTMLInputElement>
129
+ );
130
+ if (mouseDownOnFieldRef?.current) return;
131
+ onToggleOpen?.();
132
+ }}
133
+ fullWidth
134
+ />
135
+ </div>
136
+ );
137
+ }
138
+ );
139
+
140
+ const headerControlStyle: React.CSSProperties = {
141
+ display: 'flex',
142
+ alignItems: 'center',
143
+ gap: 6,
144
+ };
145
+
146
+ const getHeaderWrapperStyle = (
147
+ dpTokens: ReturnType<typeof getDatePickerTokensFromResolved>
148
+ ): React.CSSProperties => ({
149
+ display: 'flex',
150
+ alignItems: 'center',
151
+ justifyContent: 'space-between',
152
+ padding: dpTokens.header.padding,
153
+ borderBottom: dpTokens.header.borderBottom,
154
+ fontFamily: dpTokens.input.fontFamily,
155
+ fontSize: dpTokens.header.fontSize,
156
+ });
157
+
158
+ const getHeaderButtonStyle = (
159
+ dpTokens: ReturnType<typeof getDatePickerTokensFromResolved>
160
+ ): React.CSSProperties => ({
161
+ border: 'none',
162
+ background: 'transparent',
163
+ color: dpTokens.input.color,
164
+ cursor: 'pointer',
165
+ padding: dpTokens.headerButton.padding,
166
+ fontSize: dpTokens.headerButton.fontSize,
167
+ lineHeight: 1,
168
+ });
169
+
170
+ const getHeaderSelectStyle = (
171
+ dpTokens: ReturnType<typeof getDatePickerTokensFromResolved>
172
+ ): React.CSSProperties => ({
173
+ padding: dpTokens.headerSelect.padding,
174
+ borderRadius: dpTokens.headerSelect.borderRadius,
175
+ border: dpTokens.headerSelect.border,
176
+ backgroundColor: dpTokens.headerSelect.background,
177
+ fontSize: dpTokens.headerSelect.fontSize,
178
+ });
179
+
180
+ const MONTH_NAMES = [
181
+ 'January',
182
+ 'February',
183
+ 'March',
184
+ 'April',
185
+ 'May',
186
+ 'June',
187
+ 'July',
188
+ 'August',
189
+ 'September',
190
+ 'October',
191
+ 'November',
192
+ 'December',
193
+ ];
194
+
195
+ const CURRENT_YEAR = new Date().getFullYear();
196
+ const YEAR_START = CURRENT_YEAR - 50;
197
+ const YEAR_END = CURRENT_YEAR + 20;
198
+
199
+ const getAvailableYears = (minDate?: Date | null, maxDate?: Date | null): number[] => {
200
+ const start = minDate ? minDate.getFullYear() : YEAR_START;
201
+ const end = maxDate ? maxDate.getFullYear() : YEAR_END;
202
+ const years: number[] = [];
203
+ for (let y = start; y <= end; y += 1) {
204
+ years.push(y);
205
+ }
206
+ return years;
207
+ };
208
+
209
+ const isMonthInRange = (
210
+ monthIndex: number,
211
+ selectedYear: number,
212
+ minDate?: Date | null,
213
+ maxDate?: Date | null
214
+ ): boolean => {
215
+ if (minDate) {
216
+ const minYear = minDate.getFullYear();
217
+ if (selectedYear < minYear) return false;
218
+ if (selectedYear === minYear && monthIndex < minDate.getMonth()) return false;
219
+ }
220
+ if (maxDate) {
221
+ const maxYear = maxDate.getFullYear();
222
+ if (selectedYear > maxYear) return false;
223
+ if (selectedYear === maxYear && monthIndex > maxDate.getMonth()) return false;
224
+ }
225
+ return true;
226
+ };
227
+
228
+ const getHelperStyle = (
229
+ dpTokens: ReturnType<typeof getDatePickerTokensFromResolved>,
230
+ error?: boolean
231
+ ): React.CSSProperties => ({
232
+ marginTop: 4,
233
+ fontSize: dpTokens.helper.fontSize,
234
+ color: error ? dpTokens.helper.errorColor : dpTokens.helper.color,
235
+ fontFamily: dpTokens.input.fontFamily,
236
+ });
237
+
238
+ /**
239
+ * react-datepicker's DatePickerProps is a discriminated union (single | range | multi).
240
+ * Spreading the union into JSX cannot pick a branch, so we pin the default (single-date) props shape.
241
+ */
242
+ export type SingleDatePickerProps = Extract<
243
+ DatePickerProps,
244
+ { selectsRange?: never; selectsMultiple?: never }
245
+ >;
246
+
247
+ export type DatePickerWrapperProps = DatePickerProps & {
248
+ /** Helper text below the input (e.g. date format or error message). */
249
+ helperText?: React.ReactNode;
250
+ /** When true, shows error state (red border, red helper text). */
251
+ error?: boolean;
252
+ /** Label shown above the input */
253
+ label?: string;
254
+ /** When true, marks the field as required */
255
+ required?: boolean;
256
+ /** Size variant: sm, md, or lg */
257
+ size?: 'sm' | 'md' | 'lg';
258
+ /** Custom input for the DatePicker */
259
+ customInput?: React.ReactNode;
260
+ };
261
+
262
+ export function DatePicker({
263
+ helperText = '(Date Format - YYYY-MM-DD)',
264
+ placeholderText = 'Valid Upto',
265
+ dateFormat = 'yyyy-MM-dd',
266
+ isClearable = false,
267
+ error = false,
268
+ label,
269
+ required = false,
270
+ disabled = false,
271
+ size = 'md',
272
+
273
+ ...props
274
+ }: DatePickerWrapperProps) {
275
+ // Only render the helper line when the consumer actually provides a helperText
276
+ // (e.g. an error message). No automatic date-format hint fallback.
277
+ const displayHelperText: React.ReactNode = helperText;
278
+ const { renderCustomHeader, onChange, ...restProps } = props;
279
+ const [isOpen, setIsOpen] = useState(false);
280
+ const mouseDownOnFieldRef = useRef(false);
281
+ const suppressOpenOnFocusRef = useRef(false);
282
+ const containerRef = useRef<HTMLDivElement>(null);
283
+
284
+ const closeCalendar = () => {
285
+ // Selecting a value returns focus to the input; skip the focus-open once.
286
+ suppressOpenOnFocusRef.current = true;
287
+ setIsOpen(false);
288
+ };
289
+
290
+ const openCalendar = () => {
291
+ if (suppressOpenOnFocusRef.current) {
292
+ suppressOpenOnFocusRef.current = false;
293
+ return;
294
+ }
295
+ setIsOpen(true);
296
+ };
297
+
298
+ const handleChange = (date: any, event: any) => {
299
+ (onChange as any)?.(date, event);
300
+
301
+ const showTimeSelect = Boolean((restProps as { showTimeSelect?: boolean }).showTimeSelect);
302
+
303
+ if (Array.isArray(date)) {
304
+ const [start, end] = date;
305
+ if (start && end && !showTimeSelect) {
306
+ closeCalendar();
307
+ }
308
+ return;
309
+ }
310
+
311
+ if (!date) return;
312
+
313
+ if (!showTimeSelect) {
314
+ // Date-only: close after picking a day.
315
+ closeCalendar();
316
+ return;
317
+ }
318
+
319
+ // With showTimeSelect, day clicks pass a click/keyboard event; time clicks call
320
+ // onChange with no event (react-datepicker handleTimeChange). Stay open for date
321
+ // so the user can pick a time; close once the time is chosen.
322
+ // Also: controlled `open` never syncs internal state.open, so onCalendarClose
323
+ // often does not fire after time select — close explicitly here.
324
+ const isTimeListClick =
325
+ event?.target instanceof HTMLElement &&
326
+ Boolean(event.target.closest('.react-datepicker__time-list-item'));
327
+ const isTimeSelection = event == null || isTimeListClick;
328
+
329
+ if (isTimeSelection) {
330
+ closeCalendar();
331
+ }
332
+ };
333
+
334
+ useEffect(() => {
335
+ const handleMouseUp = () => {
336
+ mouseDownOnFieldRef.current = false;
337
+ };
338
+ document.addEventListener('mouseup', handleMouseUp);
339
+ return () => document.removeEventListener('mouseup', handleMouseUp);
340
+ }, []);
341
+
342
+ useEffect(() => {
343
+ const handleClickOutside = (e: MouseEvent) => {
344
+ const target = e.target as Node;
345
+ if (!containerRef.current?.contains(target)) {
346
+ const isInsideCalendar = (target as HTMLElement).closest?.('.react-datepicker');
347
+ if (!isInsideCalendar) {
348
+ setIsOpen(false);
349
+ }
350
+ }
351
+ };
352
+ document.addEventListener('mousedown', handleClickOutside);
353
+ return () => document.removeEventListener('mousedown', handleClickOutside);
354
+ }, []);
355
+
356
+ const resolvedTokens = useTokens();
357
+ const dpTokens = getDatePickerTokensFromResolved(resolvedTokens);
358
+ const tfTokens = getTextFieldTokensFromResolved(resolvedTokens);
359
+ const datePickerInputHeight =
360
+ size === 'sm' ? parseInt(tfTokens.heightSm, 10)
361
+ : size === 'lg' ? parseInt(tfTokens.heightLg, 10)
362
+ : parseInt(tfTokens.heightMd, 10);
363
+
364
+ const defaultRenderHeader = (headerProps: any) => {
365
+ const {
366
+ date,
367
+ changeYear,
368
+ changeMonth,
369
+ decreaseMonth,
370
+ increaseMonth,
371
+ prevMonthButtonDisabled,
372
+ nextMonthButtonDisabled,
373
+ } = headerProps;
374
+
375
+ const monthIndex = date.getMonth();
376
+ const year = date.getFullYear();
377
+ const { minDate, maxDate } = restProps;
378
+ const availableYears = getAvailableYears(minDate, maxDate);
379
+ const availableMonths = MONTH_NAMES.filter((_, index) =>
380
+ isMonthInRange(index, year, minDate, maxDate)
381
+ );
382
+
383
+ return (
384
+ <div style={getHeaderWrapperStyle(dpTokens)}>
385
+ <div style={headerControlStyle}>
386
+ <button
387
+ type="button"
388
+ onClick={decreaseMonth}
389
+ disabled={prevMonthButtonDisabled}
390
+ style={getHeaderButtonStyle(dpTokens)}
391
+ aria-label="Previous month"
392
+ >
393
+ ‹
394
+ </button>
395
+ <button
396
+ type="button"
397
+ onClick={increaseMonth}
398
+ disabled={nextMonthButtonDisabled}
399
+ style={getHeaderButtonStyle(dpTokens)}
400
+ aria-label="Next month"
401
+ >
402
+ ›
403
+ </button>
404
+ </div>
405
+ <div style={headerControlStyle}>
406
+ <select
407
+ value={MONTH_NAMES[monthIndex]}
408
+ onChange={(e) => {
409
+ const newMonthIndex = MONTH_NAMES.indexOf(e.target.value);
410
+ if (newMonthIndex >= 0) changeMonth(newMonthIndex);
411
+ }}
412
+ style={getHeaderSelectStyle(dpTokens)}
413
+ >
414
+ {availableMonths.map((name) => (
415
+ <option key={name} value={name}>
416
+ {name}
417
+ </option>
418
+ ))}
419
+ </select>
420
+ <select
421
+ value={year}
422
+ onChange={(e) => changeYear(Number(e.target.value))}
423
+ style={getHeaderSelectStyle(dpTokens)}
424
+ >
425
+ {availableYears.map((y) => (
426
+ <option key={y} value={y}>
427
+ {y}
428
+ </option>
429
+ ))}
430
+ </select>
431
+ </div>
432
+ </div>
433
+ );
434
+ };
435
+
436
+ const labelStyle: React.CSSProperties = {
437
+ display: 'block',
438
+ marginBottom: parseInt(String(tfTokens.labelMarginBottom), 10),
439
+ fontSize: tfTokens.labelFontSize,
440
+ fontWeight: tfTokens.labelFontWeight,
441
+ color: tfTokens.labelColor,
442
+ fontFamily: tfTokens.fontFamily,
443
+ };
444
+ const rootStyle: React.CSSProperties = {
445
+ width: '100%',
446
+ fontFamily: dpTokens.input.fontFamily,
447
+ opacity: disabled ? parseFloat(String(tfTokens.disabledOpacity)) : 1,
448
+ };
449
+
450
+ return (
451
+ <div ref={containerRef} style={rootStyle}>
452
+ {label != null && label !== '' && (
453
+ <label
454
+ onClick={(e) => {
455
+ if (disabled) return;
456
+ if (isOpen) {
457
+ setIsOpen(false);
458
+ } else {
459
+ e.preventDefault();
460
+ }
461
+ }}
462
+ style={{
463
+ ...labelStyle,
464
+ color: disabled ? 'var(--text-disabled)' : labelStyle.color,
465
+ cursor: 'default',
466
+ }}
467
+ >
468
+ {label}
469
+ {required && (
470
+ <span
471
+ style={{
472
+ color: dpTokens.label.requiredColor,
473
+ marginLeft: 4,
474
+ }}
475
+ >
476
+ *
477
+ </span>
478
+ )}
479
+ </label>
480
+ )}
481
+ <ReactDatePicker
482
+ {...(restProps as SingleDatePickerProps)}
483
+ dateFormat={dateFormat}
484
+ placeholderText={placeholderText}
485
+ isClearable={isClearable}
486
+ disabled={disabled}
487
+ open={isOpen}
488
+ preventOpenOnFocus
489
+ onChange={handleChange}
490
+ onCalendarOpen={openCalendar}
491
+ onCalendarClose={closeCalendar}
492
+ portalId="react-datepicker-portal"
493
+ customInput={
494
+ restProps.customInput ?
495
+ React.cloneElement(restProps.customInput as React.ReactElement<any>, {
496
+ isOpen,
497
+ onToggleOpen: openCalendar,
498
+ onToggleClose: closeCalendar,
499
+ mouseDownOnFieldRef,
500
+ })
501
+ : <ExampleCustomInput
502
+ className="example-custom-input"
503
+ id={restProps.id}
504
+ error={error}
505
+ disabled={disabled}
506
+ size={size}
507
+ iconName={
508
+ // When used as a time-only picker, show a clock icon instead of a calendar
509
+ (restProps as { showTimeSelectOnly?: boolean }).showTimeSelectOnly ?
510
+ 'clock'
511
+ : 'calendar'
512
+ }
513
+ mouseDownOnFieldRef={mouseDownOnFieldRef}
514
+ isOpen={isOpen}
515
+ onToggleOpen={openCalendar}
516
+ onToggleClose={closeCalendar}
517
+ />
518
+ }
519
+ renderCustomHeader={renderCustomHeader ?? defaultRenderHeader}
520
+ />
521
+ {helperText != null && helperText !== '' && (
522
+ <div
523
+ style={getHelperStyle(dpTokens, error)}
524
+ role={error ? 'alert' : undefined}
525
+ className="d-flex gap-1"
526
+ >
527
+ {/* <FirebirdIcon name="info_circle" size={16} /> */}
528
+ <span>{displayHelperText}</span>
529
+ </div>
530
+ )}
531
+ <style>
532
+ {`.react-datepicker-wrapper {
533
+ width: 100%;
534
+ }
535
+ .react-datepicker__input-container .example-custom-input input.text-field-input {
536
+ height: ${datePickerInputHeight}px;
537
+ min-height: ${datePickerInputHeight}px;
538
+ padding-top: 0 !important;
539
+ padding-bottom: 0 !important;
540
+ padding-left: ${tfTokens.iconPaddingLeft} !important;
541
+ box-sizing: border-box !important;
542
+ }
543
+ .react-datepicker__input-container input::placeholder {
544
+ color: ${dpTokens.placeholderColor};
545
+ }
546
+ .react-datepicker-popper {
547
+ z-index: 1400;
548
+ }
549
+ .react-datepicker {
550
+ font-family: ${dpTokens.input.fontFamily};
551
+ border: ${dpTokens.calendar.border};
552
+ border-radius: ${dpTokens.calendar.borderRadius};
553
+ box-shadow: 0 4px 8px rgba(0,0,0,0.1);
554
+ background-color: ${dpTokens.calendar.headerBackground};
555
+ color: ${dpTokens.input.color};
556
+ }
557
+ .react-datepicker__month-container {
558
+ background-color: ${dpTokens.calendar.headerBackground};
559
+ }
560
+ .react-datepicker__header {
561
+ background-color: ${dpTokens.calendar.headerBackground};
562
+ border-bottom: ${dpTokens.calendar.border};
563
+ border-top-left-radius: ${dpTokens.calendar.borderRadius};
564
+ border-top-right-radius: ${dpTokens.calendar.borderRadius};
565
+ padding-top: 8px;
566
+ color: ${dpTokens.input.color};
567
+ }
568
+ .react-datepicker__header select,
569
+ .react-datepicker select {
570
+ background-color: ${dpTokens.headerSelect.background};
571
+ color: ${dpTokens.input.color};
572
+ border: ${dpTokens.headerSelect.border};
573
+ }
574
+ .react-datepicker__header option,
575
+ .react-datepicker option {
576
+ background-color: ${dpTokens.headerSelect.background};
577
+ color: ${dpTokens.input.color};
578
+ }
579
+ .react-datepicker__day-names,
580
+ .react-datepicker__day-name {
581
+ color: ${dpTokens.input.color};
582
+ }
583
+ .react-datepicker__day--selected,
584
+ .react-datepicker__day--in-selecting-range,
585
+ .react-datepicker__day--in-range {
586
+ background-color: ${dpTokens.calendar.selectedBackground};
587
+ color: ${dpTokens.calendar.selectedColor} !important;
588
+ }
589
+ .react-datepicker__day--selected:hover,
590
+ .react-datepicker__day--in-selecting-range:hover,
591
+ .react-datepicker__day--in-range:hover {
592
+ background-color: ${dpTokens.calendar.selectedBackground};
593
+ }
594
+ .react-datepicker__day {
595
+ color: ${dpTokens.input.color};
596
+ }
597
+ .react-datepicker__day:hover {
598
+ background-color: ${dpTokens.calendar.selectedBackground} !important;
599
+ color: ${dpTokens.calendar.selectedColor} !important;
600
+ }
601
+ .react-datepicker__day--keyboard-selected {
602
+ background-color: ${dpTokens.calendar.selectedBackground};
603
+ color: ${dpTokens.calendar.selectedColor};
604
+ }
605
+ .react-datepicker__day--today {
606
+ font-weight: 500;
607
+ }
608
+ .react-datepicker__day--disabled {
609
+ color: ${dpTokens.calendar.disabledColor};
610
+ cursor: not-allowed;
611
+ }
612
+ .react-datepicker__triangle {
613
+ display: none;
614
+ }
615
+ .react-datepicker__time-container,
616
+ .react-datepicker__time,
617
+ .react-datepicker__time-box {
618
+ background-color: ${dpTokens.calendar.headerBackground};
619
+ color: ${dpTokens.input.color};
620
+ border-left: ${dpTokens.calendar.border};
621
+ }
622
+ .react-datepicker__time-list-item {
623
+ color: ${dpTokens.input.color};
624
+ }
625
+ .react-datepicker__time-list-item:hover {
626
+ background-color: ${dpTokens.calendar.selectedBackground} !important;
627
+ color: ${dpTokens.calendar.selectedColor} !important;
628
+ }
629
+ .react-datepicker__time-list-item--selected {
630
+ background-color: ${dpTokens.calendar.selectedBackground} !important;
631
+ color: ${dpTokens.calendar.selectedColor};
632
+ }
633
+ .react-datepicker-time__header,
634
+ .react-datepicker-time__caption {
635
+ color: ${dpTokens.input.color};
636
+ }
637
+ .react-datepicker__input-time-container,
638
+ .react-datepicker-time__input input {
639
+ color: ${dpTokens.input.color};
640
+ background-color: ${dpTokens.calendar.headerBackground};
641
+ }
642
+ .react-datepicker__header button {
643
+ color: ${dpTokens.input.color};
644
+ }
645
+ .react-datepicker__close-icon {
646
+ right: 30px;
647
+ padding: 4px;
648
+ }
649
+ .react-datepicker__close-icon::after {
650
+ background-color: ${dpTokens.helper.bgColor};
651
+ color: ${dpTokens.helper.color};
652
+ font-size: 18px;
653
+ padding: 1px;
654
+ min-width: 20px;
655
+ }
656
+ .react-datepicker__close-icon:hover::after {
657
+ background-color: ${dpTokens.label.color};
658
+ }
659
+ .react-datepicker--time-only, .react-datepicker__time-container, .react-datepicker__time-container .react-datepicker__time .react-datepicker__time-box {
660
+ width: 200px !important;
661
+ }
662
+
663
+ `}
664
+ </style>
665
+ </div>
666
+ );
667
+ }
668
+
669
+ export default DatePicker;