Skip to content

Commit

Permalink
feat(datepicker): Datepicker component (#796)
Browse files Browse the repository at this point in the history
* feat: tailwindcss

* feat: prototype datepicker

* feat: build tailwind styles in packages separatly

* refactor: update gitignore

* refactor: js -> ts

* refactor: use luxon

* feat: possible to enter times directly in the field

* fix: pass test?

* fix: add needed packages

* refactor: merge main and fix issues pointed out by linter

* feat: improve formatting and handling of dates and times

* refactor: slight improvements

* refactor: change default state

* refactor: use new datepicker (#870)

* refactor: use new datepicker

* refactor: remove unused code

* refactor: fix test

* refactor: tweak based on comments

* refactor: maybe fix tests

* refactor: maybe fix tests
  • Loading branch information
henrikbossart authored Dec 4, 2023
1 parent dfe1aae commit 7389e38
Show file tree
Hide file tree
Showing 18 changed files with 705 additions and 59 deletions.
4 changes: 2 additions & 2 deletions e2e/tests/plugin-form-Simple.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -42,7 +42,7 @@ test('Simple form', async ({ page }) => {
})

await test.step('Fill out date field', async () => {
await page.getByLabel('date').fill('2023-01-01T13:00')
await page.getByLabel('date').fill('01/01/2022')
})

await test.step('Submitting form', async () => {
Expand All @@ -63,6 +63,6 @@ test('Simple form', async ({ page }) => {
await expect(
page.getByLabel('A required checkbox (e.g. for confirmation purposes)')
).toBeChecked()
await expect(page.getByLabel('date')).toHaveValue('2023-01-01T13:00')
await expect(page.getByLabel('date')).toHaveValue('01/01/2022')
})
})
30 changes: 15 additions & 15 deletions example/index.html
Original file line number Diff line number Diff line change
@@ -1,22 +1,22 @@
<!doctype html>
<html lang="en">
<head>
<head>
<link
href="https://cdn.eds.equinor.com/font/equinor-font.css"
rel="stylesheet"
href="https://cdn.eds.equinor.com/font/equinor-font.css"
rel="stylesheet"
/>
<meta charset="utf-8" />
<meta content="width=device-width, initial-scale=1" name="viewport" />
<meta content="#000000" name="theme-color" />
<meta charset="utf-8"/>
<meta content="width=device-width, initial-scale=1" name="viewport"/>
<meta content="#000000" name="theme-color"/>
<meta
content="Web site created using create-react-app"
name="description"
content="Web site created using create-react-app"
name="description"
/>
<title>React App</title>
</head>
<body>
<noscript>You need to enable JavaScript to run this app.</noscript>
<div id="root"></div>
<script src="src/index.tsx" type="module"></script>
</body>
<title>dm-core-packages</title>
</head>
<body>
<noscript>You need to enable JavaScript to run this app.</noscript>
<div id="root"></div>
<script src="src/index.tsx" type="module"></script>
</body>
</html>
1 change: 1 addition & 0 deletions example/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@
"react-router-dom": "6.15.0",
"react-toastify": "9.1.3",
"styled-components": "^5.3.11",
"ts-node": "^10.9.1",
"tsconfig-paths-webpack-plugin": "^4.0.0"
},
"devDependencies": {
Expand Down
6 changes: 6 additions & 0 deletions example/postcss.config.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
module.exports = {
plugins: {
tailwindcss: {},
autoprefixer: {},
},
}
22 changes: 22 additions & 0 deletions example/tailwind.config.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
/** @type {import('tailwindcss').Config} */
module.exports = {
content: [
'./index.html',
'./src/**/*.{ts,js,tsx,jsx}',
'../packages/dm-core/src/**/*.{ts,js,tsx,jsx}',
'../packages/dm-core-plugins/src/**/*.{ts,js,tsx,jsx}',
],
theme: {
fontFamily: {
sans: ['Equinor', 'sans-serif'],
},
extend: {
colors: {
current: 'currentColor',
'equinor-green': '#007079',
'equinor-green-light': 'rgba(230, 250, 236, 1)',
},
},
},
plugins: [],
}
36 changes: 11 additions & 25 deletions packages/dm-core-plugins/src/form/widgets/DateTimeWidget.tsx
Original file line number Diff line number Diff line change
@@ -1,36 +1,22 @@
import React from 'react'

import { TextField } from '@equinor/eds-core-react'

import { TWidget } from '../types'
import { DateTime } from 'luxon'
import { Datepicker } from '@development-framework/dm-core'

const DateTimeWidget = (props: TWidget) => {
const { label, onChange, isDirty } = props
const onChangeHandler = (event: React.ChangeEvent<HTMLInputElement>) => {
onChange?.(new Date(event.target.value).toISOString())
}
const { label, onChange, isDirty, id, value, readOnly, helperText } = props

return (
<TextField
id={props.id}
readOnly={props.readOnly}
defaultValue={DateTime.fromISO(props.value).toFormat("yyyy-MM-dd'T'T")}
inputRef={props.inputRef}
variant={props.variant}
helperText={props.helperText}
onChange={onChangeHandler}
label={label}
type='datetime-local'
<Datepicker
id={id}
variant='datetime'
value={value}
onChange={onChange}
data-testid={`form-datetime-widget-${label}`}
style={
isDirty && props.variant !== 'error'
? {
// @ts-ignore
'--eds-input-background': '#85babf5e',
}
: {}
}
readonly={readOnly}
label={label}
helperText={helperText}
isDirty={isDirty}
/>
)
}
Expand Down
30 changes: 13 additions & 17 deletions packages/dm-core-plugins/src/job/DateRangePicker.tsx
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import React from 'react'
import { TextField } from '@equinor/eds-core-react'
import { Datepicker } from '@development-framework/dm-core'

const DateRangePicker = (props: {
setDateRange: (dateRange: { startDate: string; endDate: string }) => void
Expand All @@ -15,23 +15,19 @@ const DateRangePicker = (props: {
gap: '.5rem',
}}
>
<TextField
id='startDate'
defaultValue={value.startDate}
type='datetime-local'
onChange={(e: any) =>
setDateRange({ ...value, startDate: e.target.value })
}
label={'Valid from'}
<Datepicker
id='cron-job-start-date'
variant='datetime'
value={value.startDate}
onChange={(date) => setDateRange({ ...value, start: date })}
label='Valid from'
/>
<TextField
id='endDate'
defaultValue={value.endDate}
type='datetime-local'
onChange={(e: any) =>
setDateRange({ ...value, endDate: e.target.value })
}
label={'Valid to'}
<Datepicker
id='cron-job-end-date'
variant='datetime'
value={value.endDate}
onChange={(date) => setDateRange({ ...value, end: date })}
label='Valid to'
/>
</div>
)
Expand Down
16 changes: 16 additions & 0 deletions packages/dm-core-plugins/tailwind.config.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
/** @type {import('tailwindcss').Config} */
module.exports = {
content: ['./src/**/*.{ts,js,tsx,jsx}'],
theme: {
fontFamily: {
sans: ['Equinor', 'sans-serif'],
},
extend: {
colors: {
current: 'currentColor',
'equinor-green': '#007079',
},
},
},
plugins: [],
}
2 changes: 2 additions & 0 deletions packages/dm-core/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@
"@equinor/eds-tokens": "^0.9.0",
"axios": "^1.4.0",
"dompurify": "^3.0.6",
"lodash.debounce": "^4.0.8",
"luxon": "^3.4.3",
"react-icons": "4.10.1",
"react-oauth2-code-pkce": "^1.10.1",
Expand All @@ -29,6 +30,7 @@
"@testing-library/react": "^14.0.0",
"@types/dompurify": "^3.0.4",
"@types/jest": "^29.5.3",
"@types/lodash.debounce": "^4.0.9",
"@types/luxon": "^3.3.3",
"@types/node": "^20.10.0",
"@types/react-dom": "^18.2.7",
Expand Down
178 changes: 178 additions & 0 deletions packages/dm-core/src/components/Pickers/Datepicker/Calendar.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,178 @@
import React, { ReactElement, useState } from 'react'
import {
calendar,
CALENDAR_MONTHS,
DateSelection,
getNextMonth,
getPreviousMonth,
isSameDay,
isSameMonth,
THIS_MONTH,
THIS_YEAR,
} from './calendarUtils'
import { Icon } from '@equinor/eds-core-react'
import {
calendar_today,
chevron_down,
chevron_left,
chevron_right,
} from '@equinor/eds-icons'
import { DateTime } from 'luxon'

interface CalendarProps {
dateTime: DateTime
handleDateSelection: (selection: DateSelection) => void
}

export const Calendar = (props: CalendarProps): ReactElement => {
const { dateTime, handleDateSelection } = props
const [showMonthPicker, setShowMonthPicker] = useState(false)
const [activeMonth, setActiveMonth] = useState<number>(THIS_MONTH)
const [activeYear, setActiveYear] = useState<number>(THIS_YEAR)
const cal = calendar(activeMonth, activeYear)

const currentMonthName = Object.keys(CALENDAR_MONTHS)[activeMonth - 1]

function incrementMonth(): void {
const nextMonth = getNextMonth(activeMonth, activeYear)
setActiveMonth(nextMonth.month)
setActiveYear(nextMonth.year)
}

function decrementMonth(): void {
const prevMonth = getPreviousMonth(activeMonth, activeYear)
setActiveMonth(prevMonth.month)
setActiveYear(prevMonth.year)
}

function goToToday(): void {
const now = DateTime.now()
handleDateSelection({ day: now.day, month: now.month, year: now.year })
setActiveMonth(THIS_MONTH)
setActiveYear(THIS_YEAR)
}

return (
<div className='w-full'>
<div className='flex justify-between items-center mb-3'>
<button
type='button'
onClick={() => setShowMonthPicker(!showMonthPicker)}
className={'flex group hover:text-equinor-green items-center gap-1'}
>
<span>
{currentMonthName} {activeYear}
</span>
<span
className={
'transition-all duration-250 ' +
(showMonthPicker ? 'rotate-180' : '')
}
>
<Icon
className={
'group-hover:bg-equinor-green-light rounded-full transition-all duration-250'
}
data={chevron_down}
/>
</span>
</button>
<div className='flex gap-2 justify-between items-center'>
<button
type='button'
onClick={() => decrementMonth()}
aria-label='Previous month'
className='flex items-center rounded-full hover:bg-equinor-green-light hover:text-equinor-green'
>
<Icon data={chevron_left} />
</button>
<button
type='button'
onClick={() => goToToday()}
aria-label='Go to today'
className=' p-0.5flex items-center rounded-full hover:bg-equinor-green-light hover:text-equinor-green'
>
<Icon data={calendar_today} size={16} />
</button>

<button
type='button'
onClick={() => incrementMonth()}
aria-label='Next month'
className='flex items-center rounded-full hover:bg-equinor-green-light hover:text-equinor-green'
>
<Icon data={chevron_right} />
</button>
</div>
</div>
{showMonthPicker ? (
<>
<div className='flex flex-col mb-3'>
<span className='text-sm text-gray-600'>Year</span>
<input
className='border border-gray-300 rounded px-2 py-1'
type='number'
value={activeYear}
onChange={(event) => setActiveYear(Number(event.target.value))}
/>
</div>
<div className=''>
<span className='text-sm text-gray-600'>Month</span>
<div className='grid grid-cols-3 gap-2.5 rounded py-2'>
{Object.keys(CALENDAR_MONTHS).map((month, index) => (
<button
type='button'
onClick={() => {
setActiveMonth(index + 1)
setShowMonthPicker(false)
}}
className={
'hover:bg-equinor-green-light hover:text-equinor-green px-2 rounded py-1 ' +
(index + 1 === activeMonth
? 'bg-equinor-green-light text-equinor-green'
: '')
}
key={index}
>
{month}
</button>
))}
</div>
</div>
</>
) : (
<div className='grid grid-cols-7 gap-1'>
{cal.map((date, index) => (
<button
type='button'
onClick={() => handleDateSelection(date)}
aria-label={`${date.day}. ${
Object.keys(CALENDAR_MONTHS)[date.month - 1]
}`}
className={
'p-1.5 w-9 rounded-full appearance-none hover:bg-equinor-green-light hover:text-equinor-green ' +
(isSameDay(
DateTime.fromObject(date).toUTC().toJSDate(),
dateTime.toJSDate()
)
? 'bg-equinor-green-light text-equinor-green font-medium'
: isSameMonth(
DateTime.fromObject(date).toJSDate(),
DateTime.fromObject({
year: activeYear,
month: activeMonth,
}).toJSDate()
)
? ''
: 'text-slate-400')
}
key={index}
>
{date.day}
</button>
))}
</div>
)}
</div>
)
}
Loading

0 comments on commit 7389e38

Please sign in to comment.