goobs-frontend 0.9.24 → 0.9.25

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "goobs-frontend",
3
- "version": "0.9.24",
3
+ "version": "0.9.25",
4
4
  "type": "module",
5
5
  "description": "A comprehensive React-based libary that extends the functionality of Material-UI",
6
6
  "license": "MIT",
@@ -37,10 +37,10 @@
37
37
  "otplib": "^12",
38
38
  "react-datepicker": "^8",
39
39
  "react-qr-code": "^2",
40
- "slate": "^0.112",
41
- "slate-dom": "^0.112",
42
- "slate-history": "^0.110",
43
- "slate-react": "^0.112",
40
+ "slate": "^0.114",
41
+ "slate-dom": "^0.114",
42
+ "slate-history": "^0.113",
43
+ "slate-react": "^0.114",
44
44
  "storybook": "^8",
45
45
  "zod": "^3",
46
46
  "zod-formik-adapter": "^1"
@@ -38,8 +38,7 @@ const ColumnHeaderRow: React.FC<ColumnHeaderRowProps> = ({
38
38
  // If we're mobile, just render a single dropdown + "select all" checkbox
39
39
  if (isMobile) {
40
40
  const mobileOptions = allColumns.map(col => ({
41
- value: col.field,
42
- label: col.headerName ?? col.field,
41
+ value: col.headerName ?? col.field,
43
42
  }))
44
43
 
45
44
  // Find the currently-selected column as an object
@@ -47,7 +46,19 @@ const ColumnHeaderRow: React.FC<ColumnHeaderRowProps> = ({
47
46
  mobileOptions.find(opt => opt.value === selectedOverflowField) || null
48
47
 
49
48
  const handleMobileChange = (value: { value: string } | null) => {
50
- setSelectedOverflowField(value?.value || '')
49
+ if (value && value.value) {
50
+ // Try to find a column with matching headerName first
51
+ const matchingColumn = allColumns.find(
52
+ col => col.headerName === value.value || col.field === value.value
53
+ )
54
+
55
+ // If found, use its field property, otherwise use the value directly
56
+ setSelectedOverflowField(
57
+ matchingColumn ? matchingColumn.field : value.value
58
+ )
59
+ } else {
60
+ setSelectedOverflowField('')
61
+ }
51
62
  }
52
63
 
53
64
  return (
@@ -107,7 +118,20 @@ const ColumnHeaderRow: React.FC<ColumnHeaderRowProps> = ({
107
118
  // Desktop logic
108
119
  // ---------------------------
109
120
  const handleOverflowChange = (value: { value: string } | null) => {
110
- setSelectedOverflowField(value?.value || '')
121
+ // If using headerName for value in dropdown, we need to find the corresponding field
122
+ if (value && value.value) {
123
+ // Try to find a column with matching headerName first
124
+ const matchingColumn = overflowDesktopColumns.find(
125
+ col => col.headerName === value.value || col.field === value.value
126
+ )
127
+
128
+ // If found, use its field property, otherwise use the value directly
129
+ setSelectedOverflowField(
130
+ matchingColumn ? matchingColumn.field : value.value
131
+ )
132
+ } else {
133
+ setSelectedOverflowField('')
134
+ }
111
135
  }
112
136
 
113
137
  return (
@@ -150,8 +174,7 @@ const ColumnHeaderRow: React.FC<ColumnHeaderRowProps> = ({
150
174
  <SearchableDropdown
151
175
  label="More Columns"
152
176
  options={overflowDesktopColumns.map(oc => ({
153
- value: oc.field,
154
- label: oc.headerName ?? oc.field,
177
+ value: oc.headerName ?? oc.field,
155
178
  }))}
156
179
  defaultValue={selectedOverflowField}
157
180
  onChange={handleOverflowChange}
@@ -4,11 +4,38 @@ import DatePicker from 'react-datepicker'
4
4
  import 'react-datepicker/dist/react-datepicker.css'
5
5
  import CalendarTodayIcon from '@mui/icons-material/CalendarToday'
6
6
  import TextField, { TextFieldProps } from '../../Field/Text'
7
+ import { Box } from '@mui/material'
8
+
9
+ /**
10
+ * DateRange interface for range mode
11
+ */
12
+ export interface DateRange {
13
+ start: Date | null
14
+ end: Date | null
15
+ }
7
16
 
8
17
  export interface DateFieldProps
9
18
  extends Omit<TextFieldProps, 'onChange' | 'value' | 'endAdornment'> {
10
- onChange?: (date: Date | null) => void
11
- value?: Date | null
19
+ /**
20
+ * Callback when date changes
21
+ */
22
+ onChange?: (date: Date | null | DateRange) => void
23
+ /**
24
+ * Current date value
25
+ */
26
+ value?: Date | null | DateRange
27
+ /**
28
+ * Whether to show date range picker instead of single date
29
+ */
30
+ isRange?: boolean
31
+ /**
32
+ * Start date label (for range mode)
33
+ */
34
+ startLabel?: string
35
+ /**
36
+ * End date label (for range mode)
37
+ */
38
+ endLabel?: string
12
39
  }
13
40
 
14
41
  interface CustomInputProps {
@@ -35,6 +62,9 @@ const DateField: React.FC<DateFieldProps> = ({
35
62
  onChange,
36
63
  label = 'Select Date',
37
64
  value,
65
+ isRange = false,
66
+ startLabel = 'Start Date',
67
+ endLabel = 'End Date',
38
68
  ...rest
39
69
  }) => {
40
70
  const formatDate = (date: Date | null) => {
@@ -49,22 +79,45 @@ const DateField: React.FC<DateFieldProps> = ({
49
79
  return ''
50
80
  }
51
81
 
52
- const [selectedDate, setSelectedDate] = useState<Date>(value || new Date())
82
+ // Initialize state based on whether in range mode or single date mode
83
+ const [selectedDate, setSelectedDate] = useState<Date | null>(
84
+ isRange ? null : (value as Date | null) || new Date()
85
+ )
86
+ const [dateRange, setDateRange] = useState<DateRange>(
87
+ isRange
88
+ ? (value as DateRange) || { start: new Date(), end: new Date() }
89
+ : { start: new Date(), end: new Date() }
90
+ )
53
91
  const [isOpen, setIsOpen] = useState(false)
54
- const [inputValue, setInputValue] = useState(formatDate(selectedDate))
92
+ const [isStartDateOpen, setIsStartDateOpen] = useState(false)
93
+ const [isEndDateOpen, setIsEndDateOpen] = useState(false)
94
+ const [inputValue, setInputValue] = useState(
95
+ isRange ? '' : formatDate(selectedDate)
96
+ )
97
+ const [startDateInputValue, setStartDateInputValue] = useState(
98
+ formatDate(dateRange.start)
99
+ )
100
+ const [endDateInputValue, setEndDateInputValue] = useState(
101
+ formatDate(dateRange.end)
102
+ )
55
103
 
104
+ // Single date mode handlers
56
105
  const handleChange = (date: Date | null) => {
57
- if (date) {
58
- setSelectedDate(date)
59
- setInputValue(formatDate(date))
60
- setIsOpen(false)
61
- if (onChange) {
62
- onChange(date)
106
+ if (!isRange) {
107
+ if (date) {
108
+ setSelectedDate(date)
109
+ setInputValue(formatDate(date))
110
+ setIsOpen(false)
111
+ if (onChange) {
112
+ onChange(date)
113
+ }
63
114
  }
64
115
  }
65
116
  }
66
117
 
67
118
  const handleInputChange = (e: React.ChangeEvent<HTMLInputElement>) => {
119
+ if (isRange) return // Only for single date mode
120
+
68
121
  const input = e.target
69
122
  const newValue = e.target.value
70
123
  const selectionStart = input.selectionStart || 0
@@ -103,6 +156,117 @@ const DateField: React.FC<DateFieldProps> = ({
103
156
  }, 0)
104
157
  }
105
158
 
159
+ // Range mode handlers
160
+ const handleStartDateChange = (date: Date | null) => {
161
+ if (isRange && date) {
162
+ const newRange = { ...dateRange, start: date }
163
+ setDateRange(newRange)
164
+ setStartDateInputValue(formatDate(date))
165
+ setIsStartDateOpen(false)
166
+ if (onChange) {
167
+ onChange(newRange)
168
+ }
169
+ }
170
+ }
171
+
172
+ const handleEndDateChange = (date: Date | null) => {
173
+ if (isRange && date) {
174
+ const newRange = { ...dateRange, end: date }
175
+ setDateRange(newRange)
176
+ setEndDateInputValue(formatDate(date))
177
+ setIsEndDateOpen(false)
178
+ if (onChange) {
179
+ onChange(newRange)
180
+ }
181
+ }
182
+ }
183
+
184
+ const handleStartDateInputChange = (
185
+ e: React.ChangeEvent<HTMLInputElement>
186
+ ) => {
187
+ if (!isRange) return
188
+
189
+ const input = e.target
190
+ const newValue = e.target.value
191
+ const selectionStart = input.selectionStart || 0
192
+
193
+ setStartDateInputValue(newValue)
194
+
195
+ const parts = newValue.split('/')
196
+ if (parts.length === 3) {
197
+ const month = parseInt(parts[0], 10)
198
+ const day = parseInt(parts[1], 10)
199
+ const year = parseInt(parts[2], 10)
200
+
201
+ if (!isNaN(month) && !isNaN(day) && !isNaN(year)) {
202
+ const newDate = new Date(year, month - 1, day)
203
+ if (
204
+ newDate.getMonth() === month - 1 &&
205
+ newDate.getDate() === day &&
206
+ newDate.getFullYear() === year
207
+ ) {
208
+ const newRange = { ...dateRange, start: newDate }
209
+ setDateRange(newRange)
210
+ if (onChange) {
211
+ onChange(newRange)
212
+ }
213
+ }
214
+ }
215
+ }
216
+
217
+ setTimeout(() => {
218
+ if (selectionStart <= 2) {
219
+ input.setSelectionRange(selectionStart, selectionStart)
220
+ } else if (selectionStart <= 5) {
221
+ input.setSelectionRange(selectionStart, selectionStart)
222
+ } else {
223
+ input.setSelectionRange(selectionStart, selectionStart)
224
+ }
225
+ }, 0)
226
+ }
227
+
228
+ const handleEndDateInputChange = (e: React.ChangeEvent<HTMLInputElement>) => {
229
+ if (!isRange) return
230
+
231
+ const input = e.target
232
+ const newValue = e.target.value
233
+ const selectionStart = input.selectionStart || 0
234
+
235
+ setEndDateInputValue(newValue)
236
+
237
+ const parts = newValue.split('/')
238
+ if (parts.length === 3) {
239
+ const month = parseInt(parts[0], 10)
240
+ const day = parseInt(parts[1], 10)
241
+ const year = parseInt(parts[2], 10)
242
+
243
+ if (!isNaN(month) && !isNaN(day) && !isNaN(year)) {
244
+ const newDate = new Date(year, month - 1, day)
245
+ if (
246
+ newDate.getMonth() === month - 1 &&
247
+ newDate.getDate() === day &&
248
+ newDate.getFullYear() === year
249
+ ) {
250
+ const newRange = { ...dateRange, end: newDate }
251
+ setDateRange(newRange)
252
+ if (onChange) {
253
+ onChange(newRange)
254
+ }
255
+ }
256
+ }
257
+ }
258
+
259
+ setTimeout(() => {
260
+ if (selectionStart <= 2) {
261
+ input.setSelectionRange(selectionStart, selectionStart)
262
+ } else if (selectionStart <= 5) {
263
+ input.setSelectionRange(selectionStart, selectionStart)
264
+ } else {
265
+ input.setSelectionRange(selectionStart, selectionStart)
266
+ }
267
+ }, 0)
268
+ }
269
+
106
270
  const handleKeyDown = (e: React.KeyboardEvent<HTMLInputElement>) => {
107
271
  const input = e.currentTarget
108
272
  const selectionStart = input.selectionStart || 0
@@ -118,7 +282,120 @@ const DateField: React.FC<DateFieldProps> = ({
118
282
 
119
283
  if (e.key === 'ArrowUp' || e.key === 'ArrowDown') {
120
284
  e.preventDefault()
121
- const newDate = new Date(selectedDate)
285
+
286
+ if (!isRange) {
287
+ // Single date mode
288
+ const newDate = new Date(selectedDate || new Date())
289
+ const increment = e.key === 'ArrowUp' ? 1 : -1
290
+
291
+ switch (selectedPart) {
292
+ case 'month':
293
+ newDate.setMonth(newDate.getMonth() + increment)
294
+ break
295
+ case 'day':
296
+ newDate.setDate(newDate.getDate() + increment)
297
+ break
298
+ case 'year':
299
+ newDate.setFullYear(newDate.getFullYear() + increment)
300
+ break
301
+ }
302
+
303
+ setSelectedDate(newDate)
304
+ setInputValue(formatDate(newDate))
305
+ if (onChange) {
306
+ onChange(newDate)
307
+ }
308
+ }
309
+
310
+ setTimeout(() => {
311
+ switch (selectedPart) {
312
+ case 'month':
313
+ input.setSelectionRange(0, 2)
314
+ break
315
+ case 'day':
316
+ input.setSelectionRange(3, 5)
317
+ break
318
+ case 'year':
319
+ input.setSelectionRange(6, 10)
320
+ break
321
+ }
322
+ }, 0)
323
+ }
324
+ }
325
+
326
+ const handleStartDateKeyDown = (e: React.KeyboardEvent<HTMLInputElement>) => {
327
+ if (!isRange) return
328
+
329
+ const input = e.currentTarget
330
+ const selectionStart = input.selectionStart || 0
331
+
332
+ let selectedPart: 'month' | 'day' | 'year'
333
+ if (selectionStart <= 2) {
334
+ selectedPart = 'month'
335
+ } else if (selectionStart <= 5) {
336
+ selectedPart = 'day'
337
+ } else {
338
+ selectedPart = 'year'
339
+ }
340
+
341
+ if (e.key === 'ArrowUp' || e.key === 'ArrowDown') {
342
+ e.preventDefault()
343
+ const newDate = new Date(dateRange.start || new Date())
344
+ const increment = e.key === 'ArrowUp' ? 1 : -1
345
+
346
+ switch (selectedPart) {
347
+ case 'month':
348
+ newDate.setMonth(newDate.getMonth() + increment)
349
+ break
350
+ case 'day':
351
+ newDate.setDate(newDate.getDate() + increment)
352
+ break
353
+ case 'year':
354
+ newDate.setFullYear(newDate.getFullYear() + increment)
355
+ break
356
+ }
357
+
358
+ const newRange = { ...dateRange, start: newDate }
359
+ setDateRange(newRange)
360
+ setStartDateInputValue(formatDate(newDate))
361
+ if (onChange) {
362
+ onChange(newRange)
363
+ }
364
+
365
+ setTimeout(() => {
366
+ switch (selectedPart) {
367
+ case 'month':
368
+ input.setSelectionRange(0, 2)
369
+ break
370
+ case 'day':
371
+ input.setSelectionRange(3, 5)
372
+ break
373
+ case 'year':
374
+ input.setSelectionRange(6, 10)
375
+ break
376
+ }
377
+ }, 0)
378
+ }
379
+ }
380
+
381
+ const handleEndDateKeyDown = (e: React.KeyboardEvent<HTMLInputElement>) => {
382
+ if (!isRange) return
383
+
384
+ const input = e.currentTarget
385
+ const selectionStart = input.selectionStart || 0
386
+
387
+ let selectedPart: 'month' | 'day' | 'year'
388
+ if (selectionStart <= 2) {
389
+ selectedPart = 'month'
390
+ } else if (selectionStart <= 5) {
391
+ selectedPart = 'day'
392
+ } else {
393
+ selectedPart = 'year'
394
+ }
395
+
396
+ if (e.key === 'ArrowUp' || e.key === 'ArrowDown') {
397
+ e.preventDefault()
398
+ const newDate = new Date(dateRange.end || new Date())
122
399
  const increment = e.key === 'ArrowUp' ? 1 : -1
123
400
 
124
401
  switch (selectedPart) {
@@ -133,10 +410,11 @@ const DateField: React.FC<DateFieldProps> = ({
133
410
  break
134
411
  }
135
412
 
136
- setSelectedDate(newDate)
137
- setInputValue(formatDate(newDate))
413
+ const newRange = { ...dateRange, end: newDate }
414
+ setDateRange(newRange)
415
+ setEndDateInputValue(formatDate(newDate))
138
416
  if (onChange) {
139
- onChange(newDate)
417
+ onChange(newRange)
140
418
  }
141
419
 
142
420
  setTimeout(() => {
@@ -170,7 +448,23 @@ const DateField: React.FC<DateFieldProps> = ({
170
448
 
171
449
  const handleIconClick = (e: React.MouseEvent) => {
172
450
  e.stopPropagation()
173
- setIsOpen(true)
451
+ if (!isRange) {
452
+ setIsOpen(true)
453
+ }
454
+ }
455
+
456
+ const handleStartIconClick = (e: React.MouseEvent) => {
457
+ e.stopPropagation()
458
+ if (isRange) {
459
+ setIsStartDateOpen(true)
460
+ }
461
+ }
462
+
463
+ const handleEndIconClick = (e: React.MouseEvent) => {
464
+ e.stopPropagation()
465
+ if (isRange) {
466
+ setIsEndDateOpen(true)
467
+ }
174
468
  }
175
469
 
176
470
  const calendarIcon = (
@@ -187,6 +481,94 @@ const DateField: React.FC<DateFieldProps> = ({
187
481
  />
188
482
  )
189
483
 
484
+ const startCalendarIcon = (
485
+ <CalendarTodayIcon
486
+ onClick={handleStartIconClick}
487
+ sx={{
488
+ cursor: 'pointer',
489
+ '&:hover': {
490
+ opacity: 0.8,
491
+ },
492
+ fontSize: '20px',
493
+ color: 'black',
494
+ }}
495
+ />
496
+ )
497
+
498
+ const endCalendarIcon = (
499
+ <CalendarTodayIcon
500
+ onClick={handleEndIconClick}
501
+ sx={{
502
+ cursor: 'pointer',
503
+ '&:hover': {
504
+ opacity: 0.8,
505
+ },
506
+ fontSize: '20px',
507
+ color: 'black',
508
+ }}
509
+ />
510
+ )
511
+
512
+ if (isRange) {
513
+ return (
514
+ <Box sx={{ display: 'flex', flexDirection: 'row', gap: 2 }}>
515
+ <Box sx={{ flex: 1 }}>
516
+ <TextField
517
+ label={startLabel}
518
+ value={startDateInputValue}
519
+ onChange={handleStartDateInputChange}
520
+ endAdornment={startCalendarIcon}
521
+ slotProps={{
522
+ input: {
523
+ readOnly: false,
524
+ style: { cursor: 'text', height: '40px' },
525
+ onKeyDown: handleStartDateKeyDown,
526
+ onClick: handleClick,
527
+ },
528
+ }}
529
+ {...rest}
530
+ />
531
+ <DatePicker
532
+ selected={dateRange.start ?? undefined}
533
+ onChange={handleStartDateChange}
534
+ dateFormat="MM/dd/yyyy"
535
+ customInput={<CustomInput />}
536
+ open={isStartDateOpen}
537
+ onClickOutside={() => setIsStartDateOpen(false)}
538
+ shouldCloseOnSelect
539
+ />
540
+ </Box>
541
+ <Box sx={{ flex: 1 }}>
542
+ <TextField
543
+ label={endLabel}
544
+ value={endDateInputValue}
545
+ onChange={handleEndDateInputChange}
546
+ endAdornment={endCalendarIcon}
547
+ slotProps={{
548
+ input: {
549
+ readOnly: false,
550
+ style: { cursor: 'text', height: '40px' },
551
+ onKeyDown: handleEndDateKeyDown,
552
+ onClick: handleClick,
553
+ },
554
+ }}
555
+ {...rest}
556
+ />
557
+ <DatePicker
558
+ selected={dateRange.end ?? undefined}
559
+ onChange={handleEndDateChange}
560
+ dateFormat="MM/dd/yyyy"
561
+ customInput={<CustomInput />}
562
+ open={isEndDateOpen}
563
+ onClickOutside={() => setIsEndDateOpen(false)}
564
+ shouldCloseOnSelect
565
+ minDate={dateRange.start ?? undefined}
566
+ />
567
+ </Box>
568
+ </Box>
569
+ )
570
+ }
571
+
190
572
  return (
191
573
  <>
192
574
  <TextField
@@ -205,7 +587,7 @@ const DateField: React.FC<DateFieldProps> = ({
205
587
  {...rest}
206
588
  />
207
589
  <DatePicker
208
- selected={selectedDate}
590
+ selected={selectedDate ?? undefined}
209
591
  onChange={handleChange}
210
592
  dateFormat="MM/dd/yyyy"
211
593
  customInput={<CustomInput />}
@@ -16,6 +16,9 @@ export type TextFieldProps = (
16
16
  | OutlinedTextFieldProps
17
17
  | FilledTextFieldProps
18
18
  ) & {
19
+ /** element rendered on the left side of the input */
20
+ startAdornment?: React.ReactNode
21
+ /** element rendered on the right side of the input */
19
22
  endAdornment?: React.ReactNode
20
23
  value?: string | number | readonly string[] | undefined
21
24
  onChange?: (event: React.ChangeEvent<HTMLInputElement>) => void
@@ -89,7 +92,7 @@ const StyledMuiTextField = styled(MuiTextField, {
89
92
  }) => ({
90
93
  '& .MuiOutlinedInput-root': {
91
94
  minHeight: '40px',
92
- height: 'auto', // allow vertical expansion
95
+ height: 'auto',
93
96
  backgroundColor: backgroundcolor || 'inherit',
94
97
  color: fontcolor || 'black',
95
98
  '& .MuiSelect-icon': {
@@ -100,9 +103,7 @@ const StyledMuiTextField = styled(MuiTextField, {
100
103
  outlinecolor ||
101
104
  (hasvalue === 'true' ? 'black' : 'rgba(0, 0, 0, 0.23)'),
102
105
  ...(shrunklabelposition === 'aboveNotch' && {
103
- legend: {
104
- width: '0px !important',
105
- },
106
+ legend: { width: '0px !important' },
106
107
  }),
107
108
  },
108
109
  '&:hover fieldset': {
@@ -125,9 +126,7 @@ const StyledMuiTextField = styled(MuiTextField, {
125
126
  },
126
127
  '& .MuiInputLabel-root': {
127
128
  color: unshrunkfontcolor || 'black',
128
- '&.Mui-focused': {
129
- color: shrunkfontcolor || 'black',
130
- },
129
+ '&.Mui-focused': { color: shrunkfontcolor || 'black' },
131
130
  '&.MuiInputLabel-shrink': {
132
131
  color: shrunkfontcolor || 'black',
133
132
  ...(shrunklabelposition === 'aboveNotch' && {
@@ -157,6 +156,7 @@ const TextField = React.memo<TextFieldProps>(props => {
157
156
  error,
158
157
  disabled,
159
158
  sx,
159
+ startAdornment,
160
160
  endAdornment,
161
161
  textAlign = 'left',
162
162
  slotProps: customSlotProps = {},
@@ -183,29 +183,15 @@ const TextField = React.memo<TextFieldProps>(props => {
183
183
  )
184
184
 
185
185
  const handleChange = useCallback(
186
- (e: React.ChangeEvent<HTMLInputElement>) => {
187
- if (onChange) {
188
- onChange(e)
189
- }
190
- },
186
+ (e: React.ChangeEvent<HTMLInputElement>) => onChange?.(e),
191
187
  [onChange]
192
188
  )
193
-
194
189
  const handleFocus = useCallback(
195
- (e: React.FocusEvent<HTMLInputElement>) => {
196
- if (onFocus) {
197
- onFocus(e)
198
- }
199
- },
190
+ (e: React.FocusEvent<HTMLInputElement>) => onFocus?.(e),
200
191
  [onFocus]
201
192
  )
202
-
203
193
  const handleBlur = useCallback(
204
- (e: React.FocusEvent<HTMLInputElement>) => {
205
- if (onBlur) {
206
- onBlur(e)
207
- }
208
- },
194
+ (e: React.FocusEvent<HTMLInputElement>) => onBlur?.(e),
209
195
  [onBlur]
210
196
  )
211
197
 
@@ -214,31 +200,36 @@ const TextField = React.memo<TextFieldProps>(props => {
214
200
  }, [])
215
201
 
216
202
  const mergedSlotProps = useMemo(() => {
203
+ const adornmentSx = {
204
+ color: '#000000 !important',
205
+ '& svg': {
206
+ color: '#000000 !important',
207
+ fill: '#000000 !important',
208
+ stroke: '#000000 !important',
209
+ },
210
+ }
211
+
217
212
  const defaultSlotProps = {
218
213
  input: {
219
214
  style: inputStyle,
220
- endAdornment: endAdornment ? (
221
- <InputAdornment
222
- position="end"
223
- sx={{
224
- color: '#000000 !important',
225
- '& svg': {
226
- color: '#000000 !important',
227
- fill: '#000000 !important',
228
- stroke: '#000000 !important',
229
- },
230
- }}
231
- >
232
- {endAdornment}
233
- </InputAdornment>
234
- ) : undefined,
215
+ ...(startAdornment && {
216
+ startAdornment: (
217
+ <InputAdornment position="start" sx={adornmentSx}>
218
+ {startAdornment}
219
+ </InputAdornment>
220
+ ),
221
+ }),
222
+ ...(endAdornment && {
223
+ endAdornment: (
224
+ <InputAdornment position="end" sx={adornmentSx}>
225
+ {endAdornment}
226
+ </InputAdornment>
227
+ ),
228
+ }),
235
229
  },
236
230
  inputLabel: {
237
231
  sx: {
238
- '&.MuiInputLabel-shrink': {
239
- top: '0px',
240
- left: '0px',
241
- },
232
+ '&.MuiInputLabel-shrink': { top: '0px', left: '0px' },
242
233
  '&:not(.MuiInputLabel-shrink)': {
243
234
  transform: 'scale(1)',
244
235
  transformOrigin: 'top left',
@@ -260,7 +251,7 @@ const TextField = React.memo<TextFieldProps>(props => {
260
251
  ...(customSlotProps.inputLabel || {}),
261
252
  },
262
253
  }
263
- }, [inputStyle, endAdornment, customSlotProps])
254
+ }, [inputStyle, startAdornment, endAdornment, customSlotProps])
264
255
 
265
256
  const hasValue = Boolean(value?.toString().length).toString()
266
257
 
@@ -269,11 +260,11 @@ const TextField = React.memo<TextFieldProps>(props => {
269
260
  sx={{
270
261
  display: 'flex',
271
262
  flexDirection: 'column',
272
- justifyContent: 'flex-start', // top-aligned so errors appear below
263
+ justifyContent: 'flex-start',
273
264
  width: '100%',
274
265
  marginTop: '15px',
275
- height: 'auto', // allow expansion
276
- overflow: 'visible', // ensure error messages are visible
266
+ height: 'auto',
267
+ overflow: 'visible',
277
268
  ...sx,
278
269
  }}
279
270
  onClick={handleClick}
@@ -308,5 +299,4 @@ const TextField = React.memo<TextFieldProps>(props => {
308
299
  })
309
300
 
310
301
  TextField.displayName = 'TextField'
311
-
312
302
  export default TextField
@@ -191,7 +191,7 @@ const ShowTask: React.FC<ShowTaskProps> = ({
191
191
  teamMemberAssigned,
192
192
  nextActionDate,
193
193
  })
194
- // 5) For editing an individual comments text
194
+ // 5) For editing an individual comment's text
195
195
  const [editingCommentId, setEditingCommentId] = useState<string | null>(null)
196
196
  const [editingCommentText, setEditingCommentText] = useState('')
197
197
  // 6) For selecting revisions for each comment
@@ -248,7 +248,7 @@ const ShowTask: React.FC<ShowTaskProps> = ({
248
248
  setNewComment('')
249
249
  }
250
250
 
251
- /** Begin editing a comments text */
251
+ /** Begin editing a comment's text */
252
252
  const startEditingComment = (commentId: string, currentText: string) => {
253
253
  setEditingCommentId(commentId)
254
254
  setEditingCommentText(currentText)
@@ -1050,7 +1050,14 @@ const ShowTask: React.FC<ShowTaskProps> = ({
1050
1050
  label="Next Action Date"
1051
1051
  value={safeParseDate(formData.nextActionDate)}
1052
1052
  onChange={date => {
1053
- if (date) {
1053
+ // Check if date is a DateRange or a Date
1054
+ if (date && 'start' in date) {
1055
+ // It's a DateRange, but we're not using range mode
1056
+ return
1057
+ }
1058
+
1059
+ // Handle Date type
1060
+ if (date instanceof Date) {
1054
1061
  const mm = String(date.getMonth() + 1).padStart(2, '0')
1055
1062
  const dd = String(date.getDate()).padStart(2, '0')
1056
1063
  const yyyy = date.getFullYear()
package/src/index.ts CHANGED
@@ -71,6 +71,10 @@ import AccountNumber, {
71
71
  import RoutingNumber, {
72
72
  RoutingNumberProps,
73
73
  } from './components/Field/Number/RoutingNumber'
74
+ import PercentageField, {
75
+ PercentageFieldProps,
76
+ } from './components/Field/Percentage'
77
+ import { DateRange } from './components/Field/Date'
74
78
 
75
79
  // Add FormDataGrid import
76
80
  import FormDataGrid from './components/Form/DataGrid'
@@ -207,11 +211,14 @@ export type { MACAddressFieldProps }
207
211
  export type { VLANFieldProps }
208
212
  // NEW: Export AddTask / ShowTask / ManageTask
209
213
  export { ShowTask }
214
+ export { PercentageField }
215
+
210
216
  export type { Task }
211
217
  export type { RawCustomer }
212
218
  export type { CheckboxProps }
213
219
  export type { AccountNumberProps }
214
220
  export type { RoutingNumberProps }
221
+ export type { PercentageFieldProps }
215
222
  /* -------------------------------------------------------------------------- */
216
223
  /* Named Type Exports */
217
224
  /* -------------------------------------------------------------------------- */
@@ -223,6 +230,7 @@ export { RoutingNumber }
223
230
  export type { FormDataGridProps }
224
231
  export type { CustomDialogProps }
225
232
 
233
+ export type { DateRange }
226
234
  // 2) All DataGrid Types
227
235
  export type { DatagridProps }
228
236
  export type { ColumnDef, RowData }