Skip to content

Commit 08d9258

Browse files
committed
fix: date input
1 parent cfd1d9d commit 08d9258

File tree

6 files changed

+167
-39
lines changed

6 files changed

+167
-39
lines changed

.changeset/hot-hands-take.md

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
---
2+
"@ultraviolet/ui": patch
3+
---
4+
5+
`DateInput`: value changes onBlur instead of onChange to avoid wrong dates while the user is typing

packages/form/src/components/DateInputField/__tests__/__snapshots__/index.test.tsx.snap

Lines changed: 84 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -496,6 +496,88 @@ exports[`DateInputField > should clear field 1`] = `
496496
color: #ffffff;
497497
}
498498
499+
.emotion-67 {
500+
display: -webkit-inline-box;
501+
display: -webkit-inline-flex;
502+
display: -ms-inline-flexbox;
503+
display: inline-flex;
504+
position: relative;
505+
height: 3rem;
506+
padding: 0 1rem;
507+
-webkit-flex-direction: row;
508+
-ms-flex-direction: row;
509+
flex-direction: row;
510+
gap: 0.5rem;
511+
border-radius: 0.25rem;
512+
box-sizing: border-box;
513+
width: auto;
514+
-webkit-align-items: center;
515+
-webkit-box-align: center;
516+
-ms-flex-align: center;
517+
align-items: center;
518+
cursor: pointer;
519+
-webkit-box-pack: center;
520+
-ms-flex-pack: center;
521+
-webkit-justify-content: center;
522+
justify-content: center;
523+
outline-offset: 2px;
524+
white-space: nowrap;
525+
-webkit-text-decoration: none;
526+
text-decoration: none;
527+
font-size: 1rem;
528+
font-family: Inter,sans-serif;
529+
font-weight: 500;
530+
letter-spacing: 0;
531+
line-height: 1.5rem;
532+
paragraph-spacing: 0;
533+
text-case: none;
534+
background: #8c40ef;
535+
border: none;
536+
color: #ffffff;
537+
height: 1.5625rem;
538+
width: 100%;
539+
padding: 0;
540+
color: #727683;
541+
}
542+
543+
.emotion-67:hover {
544+
-webkit-text-decoration: none;
545+
text-decoration: none;
546+
}
547+
548+
.emotion-67:active {
549+
box-shadow: 0px 0px 0px 3px #8c40ef40;
550+
}
551+
552+
.emotion-67 .e1y1n78x0 {
553+
stroke: transparent;
554+
}
555+
556+
.emotion-67:hover,
557+
.emotion-67:active {
558+
background: #792dd4;
559+
color: #f9f9fa;
560+
}
561+
562+
.emotion-67[aria-label="in-range"] {
563+
color: #521094;
564+
background-color: #f1eefc;
565+
}
566+
567+
.emotion-67[aria-label="in-range"]:hover {
568+
color: #ffffff;
569+
background-color: #792dd4;
570+
}
571+
572+
.emotion-67[aria-label="not-current"],
573+
.emotion-67:disabled {
574+
color: #b5b7bd;
575+
}
576+
577+
.emotion-67[aria-label="selected"] {
578+
color: #ffffff;
579+
}
580+
499581
<div
500582
data-testid="testing"
501583
>
@@ -668,8 +750,8 @@ exports[`DateInputField > should clear field 1`] = `
668750
31
669751
</button>
670752
<button
671-
aria-label="neutral"
672-
class="emotion-57 emotion-58 emotion-24"
753+
aria-label="selected"
754+
class="emotion-57 emotion-67 emotion-68"
673755
type="button"
674756
>
675757
1

packages/form/src/components/DateInputField/__tests__/index.test.tsx

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -38,7 +38,7 @@ describe('DateInputField', () => {
3838
await userEvent.click(input)
3939
await userEvent.click(screen.getByText('15'))
4040
await waitFor(() => {
41-
expect(onChange).toBeCalledTimes(1)
41+
expect(onChange).toBeCalledTimes(2)
4242
})
4343

4444
expect(resultForm.current.getValues('test')).toEqual(
@@ -68,10 +68,11 @@ describe('DateInputField', () => {
6868

6969
const input = screen.getByPlaceholderText<HTMLInputElement>('YYYY-MM-DD')
7070
await userEvent.click(input)
71-
await userEvent.click(screen.getByText('15'))
7271
await userEvent.click(screen.getByText('18'))
72+
await userEvent.click(screen.getByText('15'))
73+
7374
await waitFor(() => {
74-
expect(onChange).toBeCalledTimes(2)
75+
expect(onChange).toBeCalledTimes(3)
7576
})
7677

7778
expect(resultForm.current.getValues('test')).toEqual([

packages/ui/src/components/DateInput/__tests__/index.test.tsx

Lines changed: 15 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -410,6 +410,7 @@ describe('DateInput', () => {
410410
await userEvent.click(input)
411411

412412
await userEvent.type(input, '08/21/1995')
413+
input.blur()
413414
expect(mockOnChange).toBeCalled()
414415
expect(screen.getByText('August', { exact: false })).toBeInTheDocument()
415416
})
@@ -429,6 +430,7 @@ describe('DateInput', () => {
429430
await userEvent.click(input)
430431

431432
await userEvent.type(input, '08/21/1995')
433+
input.blur()
432434
expect(mockOnChange).toBeCalled()
433435
expect(screen.getByText('August', { exact: false })).toBeInTheDocument()
434436
})
@@ -448,26 +450,31 @@ describe('DateInput', () => {
448450
await userEvent.click(input)
449451

450452
await userEvent.type(input, '2000/08')
453+
input.blur()
454+
451455
expect(mockOnChange).toBeCalled()
452456
expect(screen.getByText('2000', { exact: false })).toBeInTheDocument()
453457
})
454458

455459
test('handle correctly type in input with select range and showMonthYearPicker', async () => {
456460
const mockOnChange = vi.fn()
457461
renderWithTheme(
458-
<DateInput
459-
label="Date"
460-
placeholder="YYYY-MM-DD"
461-
selectsRange
462-
showMonthYearPicker
463-
onChange={mockOnChange}
464-
/>,
462+
<>
463+
<DateInput
464+
label="Date"
465+
placeholder="YYYY-MM-DD"
466+
selectsRange
467+
showMonthYearPicker
468+
onChange={mockOnChange}
469+
/>
470+
test
471+
</>,
465472
)
466473

467474
const input = screen.getByPlaceholderText<HTMLInputElement>('YYYY-MM-DD')
468475
await userEvent.click(input)
469-
470476
await userEvent.type(input, '2000/08')
477+
input.blur()
471478
expect(mockOnChange).toBeCalled()
472479
expect(screen.getByText('2000', { exact: false })).toBeInTheDocument()
473480
})

packages/ui/src/components/DateInput/helpers.ts

Lines changed: 48 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -66,8 +66,14 @@ export const formatValue = (
6666
) => {
6767
if (selectsRange && computedRange) {
6868
return format
69-
? `${format(computedRange.start ?? undefined) ? `${format(computedRange.start ?? undefined)} - ` : ''}${format(computedRange.end ?? undefined) ?? ''}`
70-
: `${getDateISO(showMonthYearPicker, computedRange.start ?? undefined)}${computedRange.start ? ' - ' : ''}${getDateISO(showMonthYearPicker, computedRange.end ?? undefined)}`
69+
? `${
70+
format(computedRange.start ?? undefined)
71+
? `${format(computedRange.start ?? undefined)} - `
72+
: ''
73+
}${format(computedRange.end ?? undefined) ?? ''}`
74+
: `${getDateISO(showMonthYearPicker, computedRange.start ?? undefined)}${
75+
computedRange.start ? ' - ' : ''
76+
}${getDateISO(showMonthYearPicker, computedRange.end ?? undefined)}`
7177
}
7278

7379
if (computedValue && format) {
@@ -87,3 +93,43 @@ export const styleCalendarContainer = (theme: Theme) => `
8793
border-radius: ${theme.radii.default};
8894
background-color: ${theme.colors.other.elevation.background.raised};
8995
`
96+
97+
export const createDate = (value: string, showMonthYearPicker: boolean) => {
98+
if (showMonthYearPicker) {
99+
// Force YYYY/MM (since MM/YYYY not recognised as a date in typescript)
100+
const res = value.split(/\D+/).map(val => Number.parseInt(val, 10))
101+
const year =
102+
Math.max(...res) < 100 ? Math.max(...res) + 2000 : Math.max(...res) // MM/YY should be seen as MM/20YY instead of MM/19YY
103+
104+
const month = Math.min(...res) - 1
105+
const computedDate = new Date(year, month)
106+
const isValidDate = !!computedDate.getTime()
107+
108+
return isValidDate ? computedDate : null
109+
}
110+
111+
const computedDate = new Date(value)
112+
const isValidDate = !!computedDate.getTime()
113+
114+
return isValidDate ? computedDate : null
115+
}
116+
117+
export const createDateRange = (
118+
value: string,
119+
showMonthYearPicker: boolean,
120+
) => {
121+
const [startDateInput, endDateInput] = value
122+
.split(' - ')
123+
.map(val => createDate(val, showMonthYearPicker))
124+
125+
const computedNewRange: [Date | null, Date | null] = [
126+
startDateInput instanceof Date && !Number.isNaN(startDateInput.getTime())
127+
? startDateInput
128+
: null,
129+
endDateInput instanceof Date && !Number.isNaN(endDateInput.getTime())
130+
? endDateInput
131+
: null,
132+
]
133+
134+
return computedNewRange
135+
}

packages/ui/src/components/DateInput/index.tsx

Lines changed: 11 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,12 @@ import type { ContextProps } from './Context'
1313
import { DateInputContext } from './Context'
1414
import { CalendarContent } from './components/CalendarContent'
1515
import { CalendarPopup } from './components/Popup'
16-
import { formatValue, styleCalendarContainer } from './helpers'
16+
import {
17+
createDate,
18+
createDateRange,
19+
formatValue,
20+
styleCalendarContainer,
21+
} from './helpers'
1722
import { getDays, getLocalizedMonths, getMonths } from './helpersLocale'
1823

1924
const Container = styled.div`
@@ -226,28 +231,11 @@ export const DateInput = <IsRange extends undefined | boolean>({
226231

227232
const manageOnChange = (event: ChangeEvent<HTMLInputElement>) => {
228233
const newValue = event.currentTarget.value
234+
// @ts-expect-error can't get the correct type
235+
if (!newValue) onChange?.(selectsRange ? [null, null] : null)
229236

230237
if (selectsRange) {
231-
const [startDateInput, endDateInput] = newValue.split(' - ').map(val => {
232-
if (showMonthYearPicker) {
233-
// Force YYYY/MM (since MM/YYYY not recognised as a date in typescript)
234-
const res = val.split(/\D+/).map(aa => Number.parseInt(aa, 10))
235-
236-
return new Date(Math.max(...res), Math.min(...res) - 1)
237-
}
238-
239-
return new Date(val)
240-
})
241-
242-
const computedNewRange: [Date | null, Date | null] = [
243-
startDateInput instanceof Date &&
244-
!Number.isNaN(startDateInput.getTime())
245-
? startDateInput
246-
: null,
247-
endDateInput instanceof Date && !Number.isNaN(endDateInput.getTime())
248-
? endDateInput
249-
: null,
250-
]
238+
const computedNewRange = createDateRange(newValue, showMonthYearPicker)
251239

252240
setRange({ start: computedNewRange[0], end: computedNewRange[1] })
253241
setInputValue(newValue)
@@ -263,9 +251,7 @@ export const DateInput = <IsRange extends undefined | boolean>({
263251
) => void
264252
)?.(computedNewRange, event)
265253
} else {
266-
const computedDate = Date.parse(newValue) ? new Date(newValue) : null
267-
setInputValue(newValue)
268-
setValue(computedDate)
254+
const computedDate = createDate(newValue, showMonthYearPicker)
269255

270256
if (computedDate) {
271257
setMonthToShow(computedDate.getMonth() + 1)
@@ -321,6 +307,7 @@ export const DateInput = <IsRange extends undefined | boolean>({
321307
tooltip={tooltip}
322308
autoComplete="false"
323309
onChange={manageOnChange}
310+
onBlur={onBlurInput}
324311
clearable={clearable}
325312
/>
326313
</CalendarPopup>

0 commit comments

Comments
 (0)