Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions .changeset/fruity-crabs-smoke.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
"@ultraviolet/ui": minor
---

`DateInput`: change date format from MMDDYYYY to DDMMYYYY and fix date input when min/max date
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ export const Clearable = Template.bind({})
Clearable.args = {
clearable: true,
label: 'Clearable',
placeholder: 'MM-DD-YYYY',
placeholder: 'DD-MM-YYYY',
}

Clearable.decorators = [
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -12,9 +12,10 @@ MinMax.parameters = {

MinMax.args = {
label: 'Date',
maxDate: new Date('December 25, 1995 03:24:00'),
minDate: new Date('December 12, 1995 03:24:00'),
maxDate: new Date('December 25, 1995 00:00:00'),
minDate: new Date('December 12, 1995 00:00:00'),
value: new Date('1995-12-17T03:24:00'),
onChange: console.log,
}

MinMax.decorators = [
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ import { Template } from './Template'
export const Playground = Template.bind({})

Playground.args = {
placeholder: 'MM-DD-YYYY',
placeholder: 'DD-MM-YYYY',
}

Playground.decorators = [
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,6 @@ export const Template: StoryFn<typeof DateInput> = props => (

Template.args = {
label: 'Date Input',
placeholder: 'MM-DD-YYYY',
placeholder: 'DD-MM-YYYY',
required: true,
}
Original file line number Diff line number Diff line change
Expand Up @@ -1226,7 +1226,7 @@ exports[`dateInput > renders correctly with a array of dates to exclude 1`] = `
id="_r_af_"
placeholder="YYYY-MM-DD"
type="text"
value="12/11/1995"
value="11/12/1995"
/>
<div
class="styles__q3q8043 styles__toi52u0 styles_alignItems_center_xxsmall__toi52ud styles_flexDirection_row_xxsmall__toi52u2j"
Expand Down
28 changes: 25 additions & 3 deletions packages/ui/src/components/DateInput/__tests__/helper.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ import { describe, expect, test } from 'vitest'

import {
addZero,
createDate,
formatValue,
getMonthFirstDay,
getNextMonth,
Expand Down Expand Up @@ -65,14 +66,14 @@ describe('helper functions dateInput', () => {
})

test('formatValue should work with default formatting', () => {
expect(formatValue(date, null, false, false)).toBe('11/20/2000')
expect(formatValue(date, null, false, false)).toBe('20/11/2000')
expect(formatValue(date, null, true, false)).toBe('11/2000')
expect(formatValue(date, null, false, true)).toBe('11/20/2000')
expect(formatValue(date, null, false, true)).toBe('20/11/2000')

expect(formatValue(null, rangeDate, false, false)).toBe(undefined)
expect(formatValue(null, rangeDate, true, true)).toBe('10/2000 - 10/2000')
expect(formatValue(null, rangeDate, false, true)).toBe(
'10/20/2000 - 10/31/2000',
'20/10/2000 - 31/10/2000',
)
})

Expand All @@ -87,4 +88,25 @@ describe('helper functions dateInput', () => {
'2000 - 2000',
)
})

test('createDate should work', () => {
expect(createDate('12/02/2020', false)).toEqual(new Date(2020, 1, 12))
expect(createDate('12-02-2020', false)).toEqual(new Date(2020, 1, 12))
expect(createDate('12 may 2020', false)).toEqual(new Date(2020, 4, 12))
})

test('createDate should work with min and maxDate', () => {
expect(createDate('12/02/2020', false, new Date(2020, 1, 15))).toEqual(
new Date(2020, 1, 15),
)
expect(
createDate('12/02/2020', false, undefined, new Date(2020, 1, 10)),
).toEqual(new Date(2020, 1, 10))
})

test('createdate should work with showMonthYearPicker', () => {
expect(createDate('12/2020', true)).toEqual(new Date(2020, 11, 1))
expect(createDate('12-2020', true)).toEqual(new Date(2020, 11, 1))
expect(createDate('2020/12', true)).toEqual(new Date(2020, 11, 1))
})
})
25 changes: 14 additions & 11 deletions packages/ui/src/components/DateInput/__tests__/index.test.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,9 @@ import { DateInput } from '..'

tk.freeze(new Date(1_609_503_120_000))

const DECEMBER = /December/i
const YEAR = /1995/i

describe('dateInput', () => {
test('renders correctly with default props', () => {
const { asFragment } = renderWithTheme(
Expand Down Expand Up @@ -203,7 +206,7 @@ describe('dateInput', () => {
const calendar = screen.getByRole('dialog')
expect(calendar).toBeVisible()

const visibleMonth = screen.getByText(/December/i)
const visibleMonth = screen.getByText(DECEMBER)
await userEvent.click(screen.getByTestId('previous-month'))
expect(visibleMonth.textContent).toContain('November')

Expand All @@ -230,7 +233,7 @@ describe('dateInput', () => {
const calendar = screen.getByRole('dialog')
expect(calendar).toBeVisible()

const visibleMonth = screen.getByText(/1995/i)
const visibleMonth = screen.getByText(YEAR)
await userEvent.click(screen.getByTestId('previous-month'))
expect(visibleMonth.textContent).toContain('1994')

Expand All @@ -257,20 +260,20 @@ describe('dateInput', () => {
await userEvent.click(input)

await userEvent.click(screen.getByText('15'))
expect(input.value).toBe('12/15/1995')
expect(input.value).toBe('15/12/1995')

await userEvent.click(input)

const dayFromLastMonth = screen.getAllByText('30')[0] // the first element in this array is from previous month
await userEvent.click(dayFromLastMonth)

await userEvent.click(input)
expect(input.value).toBe('11/30/1995')
expect(input.value).toBe('30/11/1995')

await userEvent.click(input)
const dayFromNextMonth = screen.getAllByText('1')[1]
await userEvent.click(dayFromNextMonth)
expect(input.value).toBe('12/01/1995')
expect(input.value).toBe('01/12/1995')
})

test('handle correctly click on date range', async () => {
Expand All @@ -290,21 +293,21 @@ describe('dateInput', () => {
expect(calendar).toBeVisible()

await userEvent.click(screen.getByText('15'))
expect(input.value).toBe('02/15/1995 - ')
expect(input.value).toBe('15/02/1995 - ')
const day = screen.getByText('27')

await userEvent.hover(day)
await userEvent.unhover(day)

await userEvent.click(day)
expect(input.value).toBe('02/15/1995 - 02/27/1995')
expect(input.value).toBe('15/02/1995 - 27/02/1995')

await userEvent.click(input)
await userEvent.click(screen.getByText('31'))
expect(input.value).toBe('01/31/1995 - ')
expect(input.value).toBe('31/01/1995 - ')

await userEvent.click(screen.getByText('20'))
expect(input.value).toBe('01/20/1995 - 01/31/1995')
expect(input.value).toBe('20/01/1995 - 31/01/1995')
})

test('render correctly with showMonthYearPicker with excluded months', async () => {
Expand Down Expand Up @@ -408,7 +411,7 @@ describe('dateInput', () => {
const input = screen.getByPlaceholderText<HTMLInputElement>('YYYY-MM-DD')
await userEvent.click(input)

await userEvent.type(input, '08/21/1995')
await userEvent.type(input, '21/08/1995')
input.blur()
expect(mockOnChange).toBeCalled()
expect(screen.getByText('August', { exact: false })).toBeInTheDocument()
Expand All @@ -428,7 +431,7 @@ describe('dateInput', () => {
const input = screen.getByPlaceholderText<HTMLInputElement>('YYYY-MM-DD')
await userEvent.click(input)

await userEvent.type(input, '08/21/1995')
await userEvent.type(input, '21/08/1995')
input.blur()
expect(mockOnChange).toBeCalled()
expect(screen.getByText('August', { exact: false })).toBeInTheDocument()
Expand Down
44 changes: 35 additions & 9 deletions packages/ui/src/components/DateInput/helpers.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
const SPLIT_REGEX = /\D+/
const DATE_SEPARATOR_REGEX = /[^a-zA-Z0-9]+/
// First day of the month for a given year
export const getMonthFirstDay = (month: number, year: number) => {
const firstDay = new Date(year, month - 1, 1).getDay()
Expand Down Expand Up @@ -43,8 +45,8 @@ const getDateISO = (showMonthYearPicker: boolean, date?: Date) => {
}

return [
addZero(date.getMonth() + 1),
addZero(date.getDate()),
addZero(date.getMonth() + 1),
date.getFullYear(),
].join('/')
}
Expand Down Expand Up @@ -84,25 +86,49 @@ export const formatValue = (
return undefined
}

export const createDate = (value: string, showMonthYearPicker: boolean) => {
const returnValidDate = (
computedDate: Date,
minDate?: Date,
maxDate?: Date,
) => {
const isValidDate = !!computedDate.getTime()

const isTooSoon = isValidDate && minDate && computedDate < minDate
const isTooLate = isValidDate && maxDate && computedDate > maxDate

if (isTooLate) {
return maxDate
}
if (isTooSoon) {
return minDate
}

return isValidDate ? computedDate : null
}

export const createDate = (
value: string,
showMonthYearPicker: boolean,
minDate?: Date,
maxDate?: Date,
) => {
if (showMonthYearPicker) {
// Force YYYY/MM (since MM/YYYY not recognised as a date in typescript)
// oxlint-disable: to fix
const res = value.split(/\D+/).map(val => Number.parseInt(val, 10))
const res = value.split(SPLIT_REGEX).map(val => Number.parseInt(val, 10))
const year =
Math.max(...res) < 100 ? Math.max(...res) + 2000 : Math.max(...res) // MM/YY should be seen as MM/20YY instead of MM/19YY

const month = Math.min(...res) - 1
const computedDate = new Date(year, month)
const isValidDate = !!computedDate.getTime()

return isValidDate ? computedDate : null
return returnValidDate(computedDate, minDate, maxDate)
}

const computedDate = new Date(value)
const isValidDate = !!computedDate.getTime()
// Cannot simply use new Date(value) since its base format is MM/DD/YYYY whereas the component uses DD/MM/YYYY
const [day, month, year] = value.split(DATE_SEPARATOR_REGEX)
const computedDate = new Date(`${month} ${day} ${year}`)

return isValidDate ? computedDate : null
return returnValidDate(computedDate, minDate, maxDate)
}

export const createDateRange = (
Expand Down
14 changes: 12 additions & 2 deletions packages/ui/src/components/DateInput/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -261,7 +261,12 @@ export const DateInput = <IsRange extends undefined | boolean>({
setYearToShow(computedNewRange[0].getFullYear())
}
} else {
const computedDate = createDate(newValue, showMonthYearPicker)
const computedDate = createDate(
newValue,
showMonthYearPicker,
minDate,
maxDate,
)

if (computedDate) {
setMonthToShow(computedDate.getMonth() + 1)
Expand All @@ -287,7 +292,12 @@ export const DateInput = <IsRange extends undefined | boolean>({
) => void
)?.(computedNewRange, undefined)
} else {
const computedDate = createDate(inputValue, showMonthYearPicker)
const computedDate = createDate(
inputValue,
showMonthYearPicker,
minDate,
maxDate,
)
;(
onChange as (date: Date | null, event?: React.SyntheticEvent) => void
)?.(computedDate, undefined)
Expand Down
5 changes: 4 additions & 1 deletion packages/ui/src/components/DateInput/styles.css.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,10 @@ import {
dayMonth,
} from './components/styles.css'

const container = style({ width: '100%' })
const container = style({
width: '100%',
height: 'fit-content',
})

const calendarContentWrapper = recipe({
base: {
Expand Down
Loading