Skip to content

Commit d95da09

Browse files
committed
Create Timeframe component (#525)
* Create Timeframe component * Fix Timeframe validations * Update Timeframe component
1 parent 6abdf3a commit d95da09

File tree

5 files changed

+369
-11
lines changed

5 files changed

+369
-11
lines changed

Diff for: src/components/ui/SignupScreen.stories.js

+12-11
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@ import { storiesOf } from '@storybook/react'
66
import SignupScreen from './SignupScreen'
77
import InputField from '../forms/InputField'
88
import Button from './Button'
9+
import Label from './Label'
910

1011
const Body = ({ children }) => (
1112
<div style={{ height: '100vh' }}>
@@ -16,11 +17,11 @@ const Body = ({ children }) => (
1617
</div>
1718
)
1819

19-
const TempLabel = () => (
20-
<div>
21-
<div><strong>Lorem Ipsum</strong></div>
22-
<div>Lorem ipsum dolor sit amet, consectetur adipiscing elit. Aliquam sed elit elementum, ultrices justo eget, condimentum ante.</div>
23-
</div>
20+
const SampleLabel = () => (
21+
<Label
22+
headline='Lorem ipsum dolor sit amet'
23+
description='Sed dui metus, pretium vel justo at, malesuada viverra sapien.'
24+
/>
2425
)
2526

2627
storiesOf('Screens and Layouts|SignupScreen', module)
@@ -31,23 +32,23 @@ storiesOf('Screens and Layouts|SignupScreen', module)
3132
subheading='Tell us a bit about your recent work experience.'
3233
>
3334
<SignupScreen.InputGroup>
34-
<TempLabel />
35+
<SampleLabel />
3536
<InputField placeholder='Input field' />
3637
</SignupScreen.InputGroup>
3738
<SignupScreen.InputGroup>
38-
<TempLabel />
39+
<SampleLabel />
3940
<InputField placeholder='Input field' />
4041
</SignupScreen.InputGroup>
4142
<SignupScreen.InputGroup>
42-
<TempLabel />
43+
<SampleLabel />
4344
<InputField placeholder='Input field' />
4445
</SignupScreen.InputGroup>
4546
<SignupScreen.InputGroup>
46-
<TempLabel />
47+
<SampleLabel />
4748
<InputField placeholder='Input field' />
4849
</SignupScreen.InputGroup>
4950
<SignupScreen.InputGroup>
50-
<TempLabel />
51+
<SampleLabel />
5152
<InputField placeholder='Input field' />
5253
</SignupScreen.InputGroup>
5354
<Button block wide size='large'>Continue »</Button>
@@ -60,7 +61,7 @@ storiesOf('Screens and Layouts|SignupScreen/Debug', module)
6061
<Body>
6162
<SignupScreen.Layout>
6263
<SignupScreen.InputGroup>
63-
<TempLabel />
64+
<SampleLabel />
6465
<InputField placeholder='Input field' />
6566
</SignupScreen.InputGroup>
6667
</SignupScreen.Layout>

Diff for: src/components/ui/Timeframe.js

+295
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,295 @@
1+
// @flow
2+
3+
import React, { PureComponent } from 'react'
4+
5+
import Label from './Label'
6+
import CustomSelector from './CustomSelector'
7+
import InputField from '../forms/InputField'
8+
9+
import { breakpoints } from '../../styles/theme'
10+
import typo from '../../styles/typo'
11+
12+
const cmz = require('cmz')
13+
14+
const cx = {
15+
wrapper: cmz(
16+
typo.baseText,
17+
`
18+
& {
19+
display: flex
20+
align-items: center
21+
width: 100%
22+
flex-wrap: wrap
23+
}
24+
25+
@media screen and (min-width: ${breakpoints.xs}) {
26+
& {
27+
flex-wrap: nowrap
28+
}
29+
}
30+
`
31+
),
32+
33+
startDate: cmz(`
34+
& {
35+
width: 100%
36+
margin: 0 0 8px
37+
}
38+
39+
@media screen and (min-width: ${breakpoints.xs}) {
40+
& {
41+
margin: 0 8px 0 0
42+
}
43+
}
44+
`),
45+
46+
startDateFields: cmz(`
47+
display: flex
48+
flex-wrap: nowrap
49+
width: 100%
50+
`),
51+
52+
endDate: cmz(`
53+
width: 100%
54+
`),
55+
56+
endDateFields: cmz(`
57+
display: flex
58+
flex-wrap: nowrap
59+
width: 100%
60+
`),
61+
62+
month: cmz(`
63+
width: calc(60% - 8px)
64+
margin: 0 8px 0 0
65+
`),
66+
67+
year: cmz(`
68+
width: 40%
69+
`),
70+
71+
present: cmz(`
72+
line-height: 62px
73+
`),
74+
75+
noEndDate: cmz(`
76+
& {
77+
display: flex
78+
justify-content: flex-end
79+
margin: 8px 0 0 0
80+
}
81+
82+
& label {
83+
font-size: 16px !important
84+
}
85+
`)
86+
}
87+
88+
type Option = {
89+
label: string,
90+
value: string | number
91+
}
92+
93+
type Props = {
94+
startDate?: Date,
95+
endDate?: Date,
96+
noEndDate?: boolean,
97+
onChange?: ({ startDate: Date, endDate: ?Date }) => void
98+
}
99+
100+
type State = {
101+
startMonth: Option | null,
102+
startYear: Option | null,
103+
endMonth: Option | null,
104+
endYear: Option | null,
105+
noEndDate: boolean
106+
}
107+
108+
const MONTHS: Array<Option> = [
109+
{ label: 'January', value: 0 },
110+
{ label: 'February', value: 1 },
111+
{ label: 'March', value: 2 },
112+
{ label: 'April', value: 3 },
113+
{ label: 'May', value: 4 },
114+
{ label: 'June', value: 5 },
115+
{ label: 'July', value: 6 },
116+
{ label: 'August', value: 7 },
117+
{ label: 'September', value: 8 },
118+
{ label: 'October', value: 9 },
119+
{ label: 'November', value: 10 },
120+
{ label: 'December', value: 11 }
121+
]
122+
123+
const currentYear = (new Date()).getFullYear()
124+
const YEARS: Array<Option> = Array.from(
125+
{ length: (1960 - currentYear) / -1 },
126+
(_, i) => {
127+
const year = currentYear + (i * -1)
128+
return { label: String(year), value: year }
129+
}
130+
)
131+
132+
class Timeframe extends PureComponent<Props, State> {
133+
state: State = {
134+
startMonth: null,
135+
startYear: null,
136+
endMonth: null,
137+
endYear: null,
138+
noEndDate: false
139+
}
140+
141+
componentDidMount () {
142+
this.updateDateValues()
143+
}
144+
145+
getOptionValue = (options: Array<Option> = [], value: ?string | ?number) => options.find(option => option.value === value)
146+
147+
updateDateValues = () => {
148+
const { startDate = '', endDate = '', noEndDate = false } = this.props
149+
150+
const startMonth = startDate ? startDate.getMonth() : undefined
151+
const startYear = startDate ? startDate.getFullYear() : undefined
152+
const endMonth = endDate ? endDate.getMonth() : undefined
153+
const endYear = endDate ? endDate.getFullYear() : undefined
154+
155+
this.setState({
156+
startMonth: this.getOptionValue(MONTHS, startMonth),
157+
startYear: this.getOptionValue(YEARS, startYear),
158+
endMonth: this.getOptionValue(MONTHS, endMonth),
159+
endYear: this.getOptionValue(YEARS, endYear),
160+
noEndDate
161+
}, this.validateValues)
162+
}
163+
164+
updateFieldValue = (field: string, options: Array<Option> = []) => ({ value }: { value: string }) => {
165+
this.setState({
166+
[field]: this.getOptionValue(options, value)
167+
}, this.validateValues)
168+
}
169+
170+
validateValues = () => {
171+
const { startMonth, startYear, endMonth, endYear } = this.state
172+
this.setState({
173+
startMonth: this.getOptionValue(MONTHS, startMonth && startMonth.value),
174+
startYear: this.getOptionValue(YEARS, startYear && startYear.value),
175+
endMonth: this.getOptionValue(this.getValidEndMonthsList(), endMonth && endMonth.value),
176+
endYear: this.getOptionValue(this.getValidEndYearsList(), endYear && endYear.value)
177+
}, this.handleOnChange)
178+
}
179+
180+
handleOnChange = () => {
181+
const { onChange } = this.props
182+
const { startMonth, startYear, endMonth, endYear, noEndDate } = this.state
183+
184+
const startDate = startMonth && startYear ? new Date() : undefined
185+
if (startMonth && startYear && startDate) {
186+
startDate.setFullYear(Number(startYear.value))
187+
startDate.setMonth(Number(startMonth.value))
188+
}
189+
190+
const endDate = endMonth && endYear ? new Date() : undefined
191+
if (endMonth && endYear && endDate) {
192+
endDate.setFullYear(Number(endYear.value))
193+
endDate.setMonth(Number(endMonth.value))
194+
}
195+
196+
if (startDate) {
197+
onChange && onChange({
198+
startDate,
199+
endDate: noEndDate ? undefined : endDate,
200+
noEndDate
201+
})
202+
}
203+
}
204+
205+
toggleNoEndDate = () => {
206+
const { endMonth, endYear, noEndDate } = this.state
207+
this.setState({
208+
endMonth: !noEndDate ? undefined : endMonth,
209+
endYear: !noEndDate ? undefined : endYear,
210+
noEndDate: !noEndDate
211+
}, this.validateValues)
212+
}
213+
214+
getValidEndMonthsList = () => {
215+
const { startMonth, startYear, endYear } = this.state
216+
if (startMonth && startYear && startYear === endYear) {
217+
return MONTHS.filter(month => Number(month.value) >= Number(startMonth.value))
218+
}
219+
return MONTHS
220+
}
221+
222+
getValidEndYearsList = () => {
223+
const { startYear } = this.state
224+
if (startYear) {
225+
return YEARS.filter(year => Number(year.value) >= Number(startYear.value))
226+
}
227+
return YEARS
228+
}
229+
230+
render () {
231+
const { startMonth, startYear, endMonth, endYear, noEndDate } = this.state
232+
return (
233+
<div>
234+
<div className={cx.wrapper}>
235+
<div className={cx.startDate}>
236+
<Label description='Start Date' />
237+
<div className={cx.startDateFields}>
238+
<div className={cx.month}>
239+
<CustomSelector
240+
placeholder={'Month'}
241+
options={MONTHS}
242+
value={startMonth}
243+
onChange={this.updateFieldValue('startMonth', MONTHS)}
244+
/>
245+
</div>
246+
<div className={cx.year}>
247+
<CustomSelector
248+
placeholder={'Year'}
249+
options={YEARS}
250+
value={startYear}
251+
onChange={this.updateFieldValue('startYear', YEARS)}
252+
/>
253+
</div>
254+
</div>
255+
</div>
256+
<div className={cx.endDate}>
257+
<Label description='End Date' />
258+
{noEndDate ? (
259+
<div className={cx.present}>Present</div>
260+
) : (
261+
<div className={cx.endDateFields}>
262+
<div className={cx.month}>
263+
<CustomSelector
264+
placeholder={'Month'}
265+
options={this.getValidEndMonthsList()}
266+
value={endMonth}
267+
onChange={this.updateFieldValue('endMonth', this.getValidEndMonthsList())}
268+
/>
269+
</div>
270+
<div className={cx.year}>
271+
<CustomSelector
272+
placeholder={'Year'}
273+
options={this.getValidEndYearsList()}
274+
value={endYear}
275+
onChange={this.updateFieldValue('endYear', this.getValidEndYearsList())}
276+
/>
277+
</div>
278+
</div>
279+
)}
280+
</div>
281+
</div>
282+
<div className={cx.noEndDate}>
283+
<InputField
284+
type='checkbox'
285+
label='Still working there'
286+
checked={noEndDate}
287+
onChange={this.toggleNoEndDate}
288+
/>
289+
</div>
290+
</div>
291+
)
292+
}
293+
}
294+
295+
export default Timeframe

0 commit comments

Comments
 (0)