Skip to content

Commit 9b6ae29

Browse files
authored
Merge pull request #60 from solved-ac/feature/date-picker
Datepicker (Draft)
2 parents 4bdd459 + 3337d09 commit 9b6ae29

File tree

9 files changed

+668
-2
lines changed

9 files changed

+668
-2
lines changed

.eslintrc.js

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,13 +18,15 @@ module.exports = {
1818
'import/no-unresolved': 0,
1919
'import/no-extraneous-dependencies': 0,
2020
'no-shadow': 0,
21+
'no-nested-ternary': 0,
2122
'react/prop-types': 0,
2223
'react/require-default-props': 0,
2324
'react/jsx-fragments': 0,
2425
'react/jsx-filename-extension': [
2526
2,
2627
{ extensions: ['.js', '.jsx', '.ts', '.tsx'] },
2728
],
29+
'react/jsx-curly-newline': 0,
2830
'jsx-a11y/no-noninteractive-element-interactions': 0,
2931
'react/jsx-props-no-spreading': 0,
3032
'@typescript-eslint/explicit-function-return-type': [
Lines changed: 107 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,107 @@
1+
import { Centering, DateSelect } from '@solved-ac/ui-react'
2+
import { Meta, StoryObj } from '@storybook/react'
3+
import React from 'react'
4+
5+
export default {
6+
title: 'Components/DateSelect',
7+
component: DateSelect,
8+
argTypes: {
9+
locale: {
10+
control: 'text',
11+
description: 'The locale to use',
12+
},
13+
weekStartsOn: {
14+
defaultValue: 0,
15+
options: [0, 1, 2, 3, 4, 5, 6],
16+
control: {
17+
type: 'select',
18+
},
19+
description: 'The day of the week to start on',
20+
},
21+
chunks: {
22+
defaultValue: 1,
23+
options: [1, 2, 3, 4, 5, 6],
24+
control: {
25+
type: 'select',
26+
},
27+
description: 'The number of months to display at once',
28+
},
29+
annotations: {
30+
control: 'array',
31+
description: 'The annotations to display',
32+
defaultValue: [],
33+
},
34+
},
35+
} as Meta<typeof DateSelect>
36+
37+
type Story = StoryObj<typeof DateSelect>
38+
39+
export const Default: Story = {
40+
render: (args) => (
41+
<Centering>
42+
<DateSelect {...args} />
43+
</Centering>
44+
),
45+
}
46+
47+
const DAY = 1000 * 60 * 60 * 24
48+
49+
export const Annotations: Story = {
50+
render: (args) => (
51+
<Centering>
52+
<DateSelect {...args} />
53+
</Centering>
54+
),
55+
args: {
56+
annotations: [
57+
{
58+
title: "I'm a title",
59+
color: '#e3f44f',
60+
start: new Date().toISOString().split('T')[0],
61+
end: new Date(Date.now() + DAY * 7).toISOString().split('T')[0],
62+
},
63+
{
64+
title: "I'm a title",
65+
color: '#c1ecff',
66+
start: new Date(Date.now() - DAY * 17).toISOString().split('T')[0],
67+
end: new Date(Date.now() - DAY * 5).toISOString().split('T')[0],
68+
},
69+
{
70+
title: "I'm a title",
71+
color: '#ffd2bd',
72+
start: new Date(Date.now() + DAY * 4).toISOString().split('T')[0],
73+
end: new Date(Date.now() + DAY * 16).toISOString().split('T')[0],
74+
},
75+
{
76+
title: "I'm a title",
77+
color: '#ffc8fb',
78+
start: new Date(Date.now() + DAY * 9).toISOString().split('T')[0],
79+
end: new Date(Date.now() + DAY * 9).toISOString().split('T')[0],
80+
},
81+
{
82+
title: "I'm a title",
83+
color: '#f9ffc1',
84+
start: new Date(Date.now() + DAY * 16).toISOString().split('T')[0],
85+
end: new Date(Date.now() + DAY * 17).toISOString().split('T')[0],
86+
},
87+
{
88+
title: "I'm a title",
89+
color: '#d7ffc0',
90+
start: new Date(Date.now() + DAY * 24).toISOString().split('T')[0],
91+
end: new Date(Date.now() + DAY * 28).toISOString().split('T')[0],
92+
},
93+
{
94+
title: "I'm a title",
95+
color: '#b9b7b4',
96+
start: new Date(Date.now() + DAY * 21).toISOString().split('T')[0],
97+
end: new Date(Date.now() + DAY * 25).toISOString().split('T')[0],
98+
},
99+
{
100+
title: "I'm a title",
101+
color: '#ffcbcb',
102+
start: new Date(Date.now() + DAY * 34).toISOString().split('T')[0],
103+
end: new Date(Date.now() + DAY * 36).toISOString().split('T')[0],
104+
},
105+
],
106+
},
107+
}
Lines changed: 124 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,124 @@
1+
import styled from '@emotion/styled'
2+
import React, { ElementType, useEffect, useState } from 'react'
3+
import { PP, PR } from '../../types/PolymorphicElementProps'
4+
import { forwardRefWithGenerics } from '../../utils/ref'
5+
import { DateSelectContext } from './DateSelectContext'
6+
import { DateSelectMonthView } from './DateSelectMonthView'
7+
8+
export interface DateRange {
9+
start: string
10+
end: string
11+
}
12+
13+
export type DateSelectValues =
14+
| {
15+
type: 'date'
16+
value: string
17+
onChange?: (value: string) => void
18+
}
19+
| {
20+
type: 'date-range'
21+
value: DateRange
22+
onChange?: (value: DateRange) => void
23+
}
24+
25+
export interface DateSelectAnnotation extends DateRange {
26+
title?: string
27+
color?: string
28+
}
29+
30+
export type DateSelectProps = DateSelectValues & {
31+
annotations?: DateSelectAnnotation[]
32+
maxAnnotationsPerDay?: number
33+
weekStartsOn?: 0 | 1 | 2 | 3 | 4 | 5 | 6
34+
locale?: string
35+
chunks?: number
36+
}
37+
38+
export type DateSelectMode = 'year' | 'month' | 'date'
39+
export type CursorMode = 'select' | 'selectStart' | 'selectEnd'
40+
41+
export type DateSelectCursor =
42+
| {
43+
mode: 'select'
44+
hover: Date | null
45+
}
46+
| {
47+
mode: 'selectStart'
48+
hover: Date | null
49+
}
50+
| {
51+
mode: 'selectEnd'
52+
valueStart: Date
53+
hover: Date | null
54+
}
55+
56+
// const toDateString = (date: Date): string => {
57+
// return date.toISOString().split('T')[0]
58+
// }
59+
60+
// const fromDateString = (date: string): Date => {
61+
// return new Date(date)
62+
// }
63+
64+
const DateSelectContainer = styled.div`
65+
user-select: none;
66+
display: flex;
67+
gap: 1em;
68+
`
69+
70+
export const DateSelect = forwardRefWithGenerics(
71+
<T extends ElementType>(props: PP<T, DateSelectProps>, ref?: PR<T>) => {
72+
const {
73+
type,
74+
value,
75+
onChange,
76+
annotations = [],
77+
maxAnnotationsPerDay = annotations.length ? 3 : 0,
78+
weekStartsOn = 0,
79+
locale,
80+
chunks = 1,
81+
...rest
82+
} = props
83+
// const theme = useTheme()
84+
85+
const [currentMode, setCurrentMode] = useState<DateSelectMode>('date')
86+
const [selectState, setSelectState] = useState<DateSelectCursor>({
87+
mode: type === 'date' ? ('select' as const) : ('selectStart' as const),
88+
hover: null,
89+
})
90+
const [cursorDate, setCursorDate] = useState<Date>(new Date())
91+
92+
useEffect(() => {
93+
setSelectState({
94+
mode: type === 'date' ? ('select' as const) : ('selectStart' as const),
95+
hover: null,
96+
})
97+
}, [type])
98+
99+
// const selectedYear = selectedDate.getFullYear()
100+
// const selectedMonth = selectedDate.getMonth()
101+
102+
return (
103+
<DateSelectContext.Provider value={props}>
104+
<DateSelectContainer {...rest} ref={ref}>
105+
{currentMode === 'date' &&
106+
new Array(chunks).fill(0).map((_, index) => (
107+
<DateSelectMonthView
108+
// eslint-disable-next-line react/no-array-index-key
109+
key={index}
110+
offset={index - Math.floor(chunks / 2)}
111+
cursorDate={cursorDate}
112+
setCursorDate={setCursorDate}
113+
selectState={selectState}
114+
setSelectState={setSelectState}
115+
setModeToMonth={() => setCurrentMode('month')}
116+
firstMonth={index === 0}
117+
lastMonth={index === chunks - 1}
118+
/>
119+
))}
120+
</DateSelectContainer>
121+
</DateSelectContext.Provider>
122+
)
123+
}
124+
)
Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
import React, { useContext } from 'react'
2+
import { DateSelectProps } from './DateSelect'
3+
4+
export const DateSelectContext = React.createContext<DateSelectProps>({
5+
type: 'date',
6+
value: '2020-06-05',
7+
onChange: () => {
8+
/* no-op */
9+
},
10+
})
11+
12+
export const useDateSelectContext = (): DateSelectProps =>
13+
useContext(DateSelectContext)

0 commit comments

Comments
 (0)