Skip to content

Commit 018da24

Browse files
[LENS-854, LENS-858] FieldDate & FieldDateRange Components (#928)
1 parent 4e9cf96 commit 018da24

File tree

15 files changed

+997
-392
lines changed

15 files changed

+997
-392
lines changed

packages/components/src/Calendar/Calendar.tsx

Lines changed: 40 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,7 @@ import 'react-day-picker/lib/style.css'
2929
import styled from 'styled-components'
3030
import has from 'lodash/has'
3131
import { mix } from 'polished'
32+
import noop from 'lodash/noop'
3233
import { reset } from '@looker/design-tokens'
3334
import { LocaleCodes } from '../utils/i18n'
3435
import { inputTextFocus } from '../Form/Inputs/InputText'
@@ -49,6 +50,7 @@ interface CalendarProps {
4950
onPrevClick?: (date: Date) => void
5051
onMonthChange?: (month: Date) => void
5152
viewMonth?: Date
53+
disabled?: boolean
5254
}
5355

5456
const NoopComponent: FC = () => null
@@ -66,18 +68,24 @@ const InternalCalendar: FC<CalendarProps> = ({
6668
onPrevClick,
6769
viewMonth,
6870
selectedDates,
71+
disabled,
6972
}) => {
7073
const renderDateRange = selectedDates && has(selectedDates, 'from')
7174
const modifiers = renderDateRange ? selectedDates : {}
7275

76+
const disableCallback = (cb: any) => {
77+
// allows provided callback to be circumvented by disabled prop
78+
return (...args: any[]) => (disabled ? noop() : cb(...args)) // eslint-disable-line standard/no-callback-literal
79+
}
80+
7381
return (
7482
<CalendarContext.Provider
7583
value={{
76-
onNextClick,
77-
onNowClick,
78-
onPrevClick,
79-
showNextButton,
80-
showPreviousButton,
84+
onNextClick: disableCallback(onNextClick),
85+
onNowClick: disableCallback(onNowClick),
86+
onPrevClick: disableCallback(onPrevClick),
87+
showNextButton: !disabled && showNextButton,
88+
showPreviousButton: !disabled && showPreviousButton,
8189
size,
8290
}}
8391
>
@@ -87,7 +95,7 @@ const InternalCalendar: FC<CalendarProps> = ({
8795
locale={locale}
8896
month={viewMonth}
8997
showOutsideDays={true}
90-
onDayClick={onDayClick}
98+
onDayClick={disableCallback(onDayClick)}
9199
navbarElement={CalendarNav}
92100
captionElement={NoopComponent}
93101
modifiers={modifiers}
@@ -107,7 +115,7 @@ export const Calendar = styled<FC<CalendarProps>>(InternalCalendar)`
107115
border: 1px solid transparent;
108116
&:focus {
109117
outline: none;
110-
${inputTextFocus}
118+
${({ disabled }) => !disabled && inputTextFocus}
111119
}
112120
}
113121
.DayPicker-Month {
@@ -132,25 +140,35 @@ export const Calendar = styled<FC<CalendarProps>>(InternalCalendar)`
132140
justify-items: center;
133141
border: 1px solid transparent;
134142
transition: background-color 110ms linear;
135-
color: ${({ theme }) => theme.colors.palette.charcoal700};
143+
color: ${({ theme, disabled }) =>
144+
disabled
145+
? theme.colors.palette.charcoal500
146+
: theme.colors.palette.charcoal700};
147+
cursor: ${({ disabled }) => (disabled ? 'default' : 'pointer')};
136148
&.DayPicker-Day--outside {
137149
color: ${({ theme }) => theme.colors.palette.charcoal300};
138150
}
139151
&--today {
140-
color: ${({ theme }) => theme.colors.semanticColors.primary.main};
152+
color: ${({ theme, disabled }) =>
153+
!disabled && theme.colors.semanticColors.primary.main};
141154
}
142155
&--selected:not(.DayPicker-Day--disabled):not(.DayPicker-Day--outside) {
143156
position: static;
144-
background-color: ${({ theme }) =>
145-
theme.colors.semanticColors.primary.main};
157+
background-color: ${({ theme, disabled }) =>
158+
disabled
159+
? theme.colors.palette.charcoal500
160+
: theme.colors.semanticColors.primary.main};
146161
&:hover {
147-
background-color: ${({ theme }) =>
148-
theme.colors.semanticColors.primary.dark};
162+
background-color: ${({ theme, disabled }) =>
163+
disabled
164+
? theme.colors.palette.charcoal500
165+
: theme.colors.semanticColors.primary.dark};
149166
}
150167
}
151168
&:focus {
152169
border-width: 2px;
153-
border-color: ${(props) => props.theme.colors.palette.purple300};
170+
border-color: ${({ theme: { colors }, disabled }) =>
171+
disabled ? colors.palette.charcoal200 : colors.palette.purple300};
154172
outline: none;
155173
}
156174
}
@@ -162,8 +180,10 @@ export const Calendar = styled<FC<CalendarProps>>(InternalCalendar)`
162180
.DayPicker-Day--selected {
163181
&.DayPicker-Day--outside,
164182
&:not(.DayPicker-Day--to):not(.DayPicker-Day--from) {
165-
background-color: ${({ theme }) =>
166-
theme.colors.semanticColors.primary.light};
183+
background-color: ${({ theme, disabled }) =>
184+
disabled
185+
? theme.colors.palette.charcoal200
186+
: theme.colors.semanticColors.primary.light};
167187
color: ${({ theme }) =>
168188
mix(
169189
0.65,
@@ -207,9 +227,10 @@ export const Calendar = styled<FC<CalendarProps>>(InternalCalendar)`
207227
&:not(.DayPicker--interactionDisabled) {
208228
.DayPicker-Day:not(.DayPicker-Day--disabled):not(.DayPicker-Day--selected):not(.DayPicker-Day--outside):hover {
209229
&:hover {
210-
background-color: ${({ theme }) =>
211-
theme.colors.semanticColors.primary.light};
212-
color: ${({ theme }) => theme.colors.semanticColors.primary.main};
230+
background-color: ${({ theme, disabled }) =>
231+
disabled ? 'transparent' : theme.colors.semanticColors.primary.light};
232+
color: ${({ theme, disabled }) =>
233+
!disabled && theme.colors.semanticColors.primary.main};
213234
}
214235
}
215236
}
Lines changed: 112 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,112 @@
1+
/*
2+
3+
MIT License
4+
5+
Copyright (c) 2020 Looker Data Sciences, Inc.
6+
7+
Permission is hereby granted, free of charge, to any person obtaining a copy
8+
of this software and associated documentation files (the "Software"), to deal
9+
in the Software without restriction, including without limitation the rights
10+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
11+
copies of the Software, and to permit persons to whom the Software is
12+
furnished to do so, subject to the following conditions:
13+
14+
The above copyright notice and this permission notice shall be included in all
15+
copies or substantial portions of the Software.
16+
17+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
18+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
19+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
20+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
21+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
22+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
23+
SOFTWARE.
24+
25+
*/
26+
27+
import 'jest-styled-components'
28+
import React from 'react'
29+
import { mountWithTheme, renderWithTheme } from '@looker/components-test-utils'
30+
import { FieldDate } from './FieldDate'
31+
32+
const realDateNow = Date.now.bind(global.Date)
33+
34+
beforeEach(() => {
35+
/* eslint-disable-next-line @typescript-eslint/unbound-method */
36+
global.Date.now = jest.fn(() => 1580517818172)
37+
})
38+
39+
afterEach(() => {
40+
/* eslint-disable-next-line @typescript-eslint/unbound-method */
41+
global.Date.now = realDateNow
42+
jest.clearAllMocks()
43+
})
44+
45+
test('FieldDate renders and displays label', () => {
46+
const wrapper = mountWithTheme(
47+
<FieldDate
48+
defaultValue={new Date(Date.now())}
49+
id="FieldDateID"
50+
label="Test Label"
51+
/>
52+
)
53+
54+
expect(wrapper.text()).toMatch(`Test Label`)
55+
})
56+
57+
test('FieldDate should accept detail and description attributes', () => {
58+
const { getByLabelText } = renderWithTheme(
59+
<FieldDate
60+
defaultValue={new Date(Date.now())}
61+
description="this is the description"
62+
detail="5/50"
63+
id="FieldDateID"
64+
label="Label"
65+
/>
66+
)
67+
68+
const input = getByLabelText('Label')
69+
expect(input.getAttribute('detail')).toBeDefined()
70+
expect(input.getAttribute('description')).toBeDefined()
71+
})
72+
73+
test('FieldDate should accept a disabled prop', () => {
74+
const { getByLabelText } = renderWithTheme(
75+
<FieldDate
76+
defaultValue={new Date(Date.now())}
77+
disabled
78+
id="FieldDateID"
79+
label="Disabled Label"
80+
/>
81+
)
82+
83+
const input = getByLabelText('Disabled Label')
84+
expect(input.getAttribute('disabled')).toBeDefined()
85+
})
86+
87+
test('FieldDate should accept required attributes', () => {
88+
const { getByText } = renderWithTheme(
89+
<FieldDate
90+
defaultValue={new Date(Date.now())}
91+
id="FieldDateID"
92+
label="Required Label"
93+
required
94+
/>
95+
)
96+
expect(getByText('required')).toBeVisible()
97+
})
98+
99+
test('FieldDate should display error message', () => {
100+
const errorMessage = 'This is an error'
101+
102+
const { getByText } = renderWithTheme(
103+
<FieldDate
104+
defaultValue={new Date(Date.now())}
105+
id="FieldDateID"
106+
label="Validation Label"
107+
validationMessage={{ message: errorMessage, type: 'error' }}
108+
/>
109+
)
110+
111+
expect(getByText('This is an error')).toBeVisible()
112+
})
Lines changed: 60 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,60 @@
1+
/*
2+
3+
MIT License
4+
5+
Copyright (c) 2020 Looker Data Sciences, Inc.
6+
7+
Permission is hereby granted, free of charge, to any person obtaining a copy
8+
of this software and associated documentation files (the "Software"), to deal
9+
in the Software without restriction, including without limitation the rights
10+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
11+
copies of the Software, and to permit persons to whom the Software is
12+
furnished to do so, subject to the following conditions:
13+
14+
The above copyright notice and this permission notice shall be included in all
15+
copies or substantial portions of the Software.
16+
17+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
18+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
19+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
20+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
21+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
22+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
23+
SOFTWARE.
24+
25+
*/
26+
27+
import React, { forwardRef, Ref } from 'react'
28+
import styled from 'styled-components'
29+
import { useID } from '../../../utils'
30+
import { useFormContext } from '../..'
31+
import { InputDate, InputDateProps } from '../../Inputs/InputDate'
32+
import { Field, FieldProps, omitFieldProps, pickFieldProps } from '../Field'
33+
34+
export interface FieldInputDateProps extends FieldProps, InputDateProps {}
35+
36+
const FieldDateComponent = forwardRef(
37+
(props: FieldInputDateProps, ref: Ref<HTMLInputElement>) => {
38+
const validationMessage = useFormContext(props)
39+
const id = useID(props.id)
40+
return (
41+
<Field
42+
{...pickFieldProps(props)}
43+
id={id}
44+
validationMessage={validationMessage}
45+
>
46+
<InputDate
47+
{...omitFieldProps(props)}
48+
aria-describedby={`${id}-describedby`}
49+
id={id}
50+
validationType={validationMessage && validationMessage.type}
51+
ref={ref}
52+
/>
53+
</Field>
54+
)
55+
}
56+
)
57+
58+
FieldDateComponent.displayName = 'FieldDateComponent'
59+
60+
export const FieldDate = styled(FieldDateComponent)``
Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,27 @@
1+
/*
2+
3+
MIT License
4+
5+
Copyright (c) 2020 Looker Data Sciences, Inc.
6+
7+
Permission is hereby granted, free of charge, to any person obtaining a copy
8+
of this software and associated documentation files (the "Software"), to deal
9+
in the Software without restriction, including without limitation the rights
10+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
11+
copies of the Software, and to permit persons to whom the Software is
12+
furnished to do so, subject to the following conditions:
13+
14+
The above copyright notice and this permission notice shall be included in all
15+
copies or substantial portions of the Software.
16+
17+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
18+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
19+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
20+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
21+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
22+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
23+
SOFTWARE.
24+
25+
*/
26+
27+
export * from './FieldDate'

0 commit comments

Comments
 (0)