Skip to content

Commit aa7dd3e

Browse files
committed
feat: calendar skeleton
1 parent 4bdd459 commit aa7dd3e

File tree

5 files changed

+230
-0
lines changed

5 files changed

+230
-0
lines changed
Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
import { Centering, DateSelect } from '@solved-ac/ui-react'
2+
import { Meta, StoryFn } from '@storybook/react'
3+
import React from 'react'
4+
5+
export default {
6+
title: 'Components/DateSelect',
7+
component: DateSelect,
8+
argTypes: {},
9+
} as Meta<typeof DateSelect>
10+
11+
const Template: StoryFn<typeof DateSelect> = (args) => (
12+
<Centering>
13+
<DateSelect {...args} />
14+
</Centering>
15+
)
16+
17+
export const Default = Template.bind({})
Lines changed: 75 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,75 @@
1+
import React, { ElementType, useState } from 'react'
2+
import { PP, PR } from '../../types/PolymorphicElementProps'
3+
import { forwardRefWithGenerics } from '../../utils/ref'
4+
import { DateSelectMonthView } from './DateSelectMonthView'
5+
6+
export interface DateRange {
7+
start: string
8+
end: string
9+
}
10+
11+
export type DateSelectValues =
12+
| {
13+
type: 'date'
14+
value: string
15+
onChange: (value: string) => void
16+
}
17+
| {
18+
type: 'date-range'
19+
value: DateRange
20+
onChange: (value: DateRange) => void
21+
}
22+
23+
export interface DateSelectAnnotation extends DateRange {
24+
title?: string
25+
color?: string
26+
}
27+
28+
export type DateSelectProps = DateSelectValues & {
29+
annotations?: DateSelectAnnotation[]
30+
weekStartsOn?: 0 | 1 | 2 | 3 | 4 | 5 | 6
31+
locale?: string
32+
}
33+
34+
export type DateSelectMode = 'year' | 'month' | 'date'
35+
36+
// const toDateString = (date: Date): string => {
37+
// return date.toISOString().split('T')[0]
38+
// }
39+
40+
// const fromDateString = (date: string): Date => {
41+
// return new Date(date)
42+
// }
43+
44+
export const DateSelect = forwardRefWithGenerics(
45+
<T extends ElementType>(props: PP<T, DateSelectProps>, ref?: PR<T>) => {
46+
const {
47+
type,
48+
value,
49+
onChange,
50+
annotations = [],
51+
weekStartsOn = 0,
52+
locale,
53+
...rest
54+
} = props
55+
// const theme = useTheme()
56+
57+
// const [currentMode, setCurrentMode] = useState<DateSelectMode>('date')
58+
const [selectedDate, setSelectedDate] = useState<Date>(new Date())
59+
60+
// const selectedYear = selectedDate.getFullYear()
61+
// const selectedMonth = selectedDate.getMonth()
62+
63+
return (
64+
<div {...rest} ref={ref}>
65+
<DateSelectMonthView
66+
{...props}
67+
selectedDate={selectedDate}
68+
setSelectedDate={setSelectedDate}
69+
firstMonth
70+
lastMonth
71+
/>
72+
</div>
73+
)
74+
}
75+
)
Lines changed: 135 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,135 @@
1+
import styled from '@emotion/styled'
2+
import { IconArrowLeft, IconArrowRight } from '@tabler/icons-react'
3+
import { ellipsis } from 'polished'
4+
import React from 'react'
5+
import { Button } from '../Button'
6+
import { Typo } from '../Typo'
7+
import { DateSelectProps } from './DateSelect'
8+
9+
const DateSelectGrid = styled.div`
10+
display: grid;
11+
grid-template-columns: repeat(7, 1fr);
12+
grid-template-rows: repeat(7, 1fr);
13+
`
14+
15+
const DateContainer = styled.div`
16+
display: flex;
17+
justify-content: center;
18+
align-items: center;
19+
min-width: 2em;
20+
min-height: 2em;
21+
`
22+
23+
const MonthCaptionContainer = styled.div`
24+
display: flex;
25+
align-items: center;
26+
`
27+
28+
const MonthCaption = styled.div`
29+
${ellipsis()}
30+
flex: 1;
31+
min-width: 0;
32+
display: flex;
33+
justify-content: center;
34+
align-items: center;
35+
`
36+
37+
const MonthNavigationButton = styled(Button)`
38+
width: 3em;
39+
height: 3em;
40+
font-size: 1em;
41+
padding: 0;
42+
display: flex;
43+
justify-content: center;
44+
align-items: center;
45+
`
46+
47+
const DAY = 24 * 60 * 60 * 1000
48+
49+
type DateSelectMonthView = DateSelectProps & {
50+
selectedDate: Date
51+
setSelectedDate: (date: Date) => void
52+
firstMonth: boolean
53+
lastMonth: boolean
54+
}
55+
56+
export const DateSelectMonthView = (
57+
props: DateSelectMonthView
58+
): JSX.Element => {
59+
const {
60+
// value,
61+
// onChange,
62+
// annotations = [],
63+
weekStartsOn = 0,
64+
selectedDate,
65+
// setSelectedDate,
66+
locale,
67+
} = props
68+
69+
const selectedYear = selectedDate.getFullYear()
70+
const selectedMonth = selectedDate.getMonth()
71+
const currentMonthLastDate = new Date(selectedYear, selectedMonth + 1, 0)
72+
// const currentMonthDates = currentMonthLastDate.getDate()
73+
74+
// 0 = sunday
75+
const currentMonth1stDate = new Date(selectedYear, selectedMonth, 1)
76+
const currentMonth1stDay = currentMonth1stDate.getDay()
77+
const firstWeekFirstDateDelta = (7 + currentMonth1stDay - weekStartsOn) % 7
78+
const firstWeekFirstDateCandidate = new Date(
79+
selectedYear,
80+
selectedMonth,
81+
1 - firstWeekFirstDateDelta
82+
)
83+
84+
const minimumRenderCalendarDateCount =
85+
currentMonthLastDate.getTime() / DAY -
86+
firstWeekFirstDateCandidate.getTime() / DAY +
87+
1
88+
89+
const currentMonthRenderWeeks = Math.ceil(minimumRenderCalendarDateCount / 7)
90+
const firstWeekFirstDate = new Date(
91+
firstWeekFirstDateCandidate.getTime() +
92+
(currentMonthRenderWeeks === 4 ? -7 : 0) * DAY
93+
)
94+
95+
return (
96+
<React.Fragment>
97+
<MonthCaptionContainer>
98+
<MonthNavigationButton circle transparent>
99+
<IconArrowLeft />
100+
</MonthNavigationButton>
101+
<MonthCaption>
102+
{currentMonth1stDate.toLocaleDateString(locale, {
103+
month: 'long',
104+
year: 'numeric',
105+
})}
106+
</MonthCaption>
107+
<MonthNavigationButton circle transparent>
108+
<IconArrowRight />
109+
</MonthNavigationButton>
110+
</MonthCaptionContainer>
111+
<DateSelectGrid>
112+
{new Array(7).fill(undefined).map((_, dayOffset) => {
113+
const date = new Date(firstWeekFirstDate.getTime() + dayOffset * DAY)
114+
return (
115+
<DateContainer key={date.toISOString()}>
116+
<Typo small>
117+
{date.toLocaleDateString(locale, { weekday: 'short' })}
118+
</Typo>
119+
</DateContainer>
120+
)
121+
})}
122+
{new Array(42).fill(undefined).map((_, dateOffset) => {
123+
const date = new Date(firstWeekFirstDate.getTime() + dateOffset * DAY)
124+
return (
125+
<DateContainer key={date.toISOString()}>
126+
<Typo description={date.getMonth() !== selectedMonth}>
127+
{date.getDate()}
128+
</Typo>
129+
</DateContainer>
130+
)
131+
})}
132+
</DateSelectGrid>
133+
</React.Fragment>
134+
)
135+
}
Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
export * from './DateSelect';
2+

src/components/index.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
1+
export * from './$DateSelect'
12
export * from './$Item'
23
export * from './$List'
34
export * from './$Tab'

0 commit comments

Comments
 (0)