Skip to content

Commit 093e90b

Browse files
authored
Added select component (#42)
* Improved external module test
1 parent 0764653 commit 093e90b

File tree

8 files changed

+250
-7
lines changed

8 files changed

+250
-7
lines changed

rollup.config.js

Lines changed: 18 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -7,17 +7,30 @@ const { MODULE_FORMAT } = process.env
77

88
const suffix = MODULE_FORMAT === 'cjs' ? '.cjs' : ''
99

10+
const externalDeps = [
11+
...Object.keys(dependencies),
12+
...Object.keys(peerDependencies),
13+
]
14+
1015
const createConfig = (input, outputFile) => ({
1116
input,
1217
output: {
1318
file: outputFile,
1419
format: MODULE_FORMAT,
1520
},
16-
external: [
17-
...Object.keys(dependencies),
18-
...Object.keys(peerDependencies),
19-
'react-dom/server',
20-
],
21+
external: module => {
22+
let isExternal = /node_module/.test(module)
23+
24+
if (!isExternal) {
25+
externalDeps.forEach(dep => {
26+
if (new RegExp(`^${dep}`).test(module)) {
27+
isExternal = true
28+
}
29+
})
30+
}
31+
32+
return isExternal
33+
},
2134
plugins: [
2235
resolve({
2336
extensions: ['.js', '.jsx'],

src/components/form/Select.jsx

Lines changed: 136 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,136 @@
1+
import React from 'react'
2+
import PropTypes from 'prop-types'
3+
import classnames from 'classnames'
4+
5+
import { withTheme } from '../theme'
6+
import { BaseComponent } from '../tailwind'
7+
8+
// https://material.io/tools/icons/?style=baseline
9+
const ExpandMore = props => (
10+
<svg
11+
xmlns="http://www.w3.org/2000/svg"
12+
width="24"
13+
height="24"
14+
viewBox="0 0 24 24"
15+
{...props}
16+
>
17+
<path d="M16.59 8.59L12 13.17 7.41 8.59 6 10l6 6 6-6z" />
18+
<path d="M0 0h24v24H0z" fill="none" />
19+
</svg>
20+
)
21+
22+
const TextInput = ({
23+
theme,
24+
is,
25+
field,
26+
children,
27+
className,
28+
id,
29+
name,
30+
type,
31+
disabled,
32+
readOnly,
33+
invalid,
34+
placeholder,
35+
options,
36+
icon,
37+
...rest
38+
}) => {
39+
const describedBy = [field.errorId, field.helpId].filter(by => by)
40+
const isInvalid = field.invalid || invalid
41+
42+
const ChevronDown = icon
43+
44+
return (
45+
<div className={`relative mb-${theme.spacing.sm}`}>
46+
<BaseComponent
47+
is={is}
48+
className={classnames(
49+
'appearance-none',
50+
disabled && 'pointer-events-none',
51+
className,
52+
)}
53+
bg="white"
54+
rounded={theme.radius}
55+
text={theme.textColors.body}
56+
p={{ l: theme.spacing.md, r: theme.spacing.lg, y: theme.spacing.sm }}
57+
border={!isInvalid ? true : [true, theme.brandColors.danger]}
58+
w="full"
59+
leading="tight"
60+
opacity={disabled ? 50 : undefined}
61+
id={field.inputId || id || name}
62+
name={name}
63+
type={type}
64+
disabled={field.disabled || disabled}
65+
readOnly={readOnly}
66+
aria-invalid={isInvalid || undefined}
67+
aria-describedby={
68+
describedBy.length ? describedBy.join(' ') : undefined
69+
}
70+
{...rest}
71+
>
72+
{!!placeholder && <option value="">{placeholder}</option>}
73+
{options.map(option => (
74+
<option key={`${name}-${option.value}`} value={option.value}>
75+
{option.label}
76+
</option>
77+
))}
78+
</BaseComponent>
79+
<div
80+
className={classnames(
81+
'pointer-events-none',
82+
'absolute',
83+
'pin-y pin-r',
84+
'flex items-center',
85+
`px-${theme.spacing.sm}`,
86+
)}
87+
>
88+
<ChevronDown className="h-6 w-6" />
89+
</div>
90+
</div>
91+
)
92+
}
93+
94+
TextInput.propTypes = {
95+
theme: PropTypes.shape({}).isRequired,
96+
is: PropTypes.oneOfType([PropTypes.string, PropTypes.func, PropTypes.object]),
97+
field: PropTypes.shape({
98+
inputId: PropTypes.string,
99+
invalid: PropTypes.bool,
100+
disabled: PropTypes.bool,
101+
}),
102+
children: PropTypes.node,
103+
className: PropTypes.string,
104+
id: PropTypes.string,
105+
name: PropTypes.string.isRequired,
106+
type: PropTypes.string,
107+
disabled: PropTypes.bool,
108+
readOnly: PropTypes.bool,
109+
invalid: PropTypes.bool,
110+
placeholder: PropTypes.oneOfType([PropTypes.string, PropTypes.bool]),
111+
icon: PropTypes.node,
112+
options: PropTypes.arrayOf(
113+
PropTypes.shape({
114+
label: PropTypes.string,
115+
value: PropTypes.string,
116+
}),
117+
),
118+
}
119+
120+
TextInput.defaultProps = {
121+
is: 'select',
122+
field: {},
123+
children: undefined,
124+
className: undefined,
125+
id: undefined,
126+
type: 'text',
127+
disabled: false,
128+
readOnly: false,
129+
invalid: false,
130+
placeholder: 'Please select',
131+
icon: ExpandMore,
132+
options: [],
133+
}
134+
135+
export { TextInput as component }
136+
export default withTheme(TextInput)

src/components/form/TextInput.jsx

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,7 @@ const TextInput = ({
3030
disabled && 'pointer-events-none',
3131
className,
3232
)}
33+
bg="white"
3334
rounded={theme.radius}
3435
text={theme.textColors.body}
3536
p={{ x: theme.spacing.md, y: theme.spacing.sm }}
Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,34 @@
1+
import React from 'react'
2+
import { shallow } from 'enzyme'
3+
4+
import { component as TextInput } from '../TextInput'
5+
import { defaultTheme } from '../../theme'
6+
7+
const setup = (testProps = {}) => {
8+
const props = Object.assign(
9+
{
10+
theme: defaultTheme,
11+
name: 'username',
12+
options: [
13+
{ value: 'one', label: 'single' },
14+
{ value: 'two', label: 'double' },
15+
],
16+
},
17+
testProps,
18+
)
19+
20+
const wrapper = shallow(<TextInput {...props} />)
21+
22+
return {
23+
props,
24+
wrapper,
25+
}
26+
}
27+
28+
describe('TextInput', () => {
29+
it('renders matching snapshot', () => {
30+
const { wrapper } = setup()
31+
32+
expect(wrapper).toMatchSnapshot()
33+
})
34+
})
Lines changed: 42 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,42 @@
1+
// Jest Snapshot v1, https://goo.gl/fbAQLP
2+
3+
exports[`TextInput renders matching snapshot 1`] = `
4+
<BaseComponent
5+
bg="white"
6+
border={true}
7+
className="appearance-none"
8+
disabled={false}
9+
id="username"
10+
is="input"
11+
leading="tight"
12+
m={
13+
Object {
14+
"b": 2,
15+
}
16+
}
17+
name="username"
18+
options={
19+
Array [
20+
Object {
21+
"label": "single",
22+
"value": "one",
23+
},
24+
Object {
25+
"label": "double",
26+
"value": "two",
27+
},
28+
]
29+
}
30+
p={
31+
Object {
32+
"x": 4,
33+
"y": 2,
34+
}
35+
}
36+
readOnly={false}
37+
rounded="rounded"
38+
text="grey-darkest"
39+
type="text"
40+
w="full"
41+
/>
42+
`;

src/components/form/__tests__/__snapshots__/TextInput.jsx.snap

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22

33
exports[`TextInput renders matching snapshot 1`] = `
44
<BaseComponent
5+
bg="white"
56
border={true}
67
className="appearance-none"
78
disabled={false}

src/components/form/index.js

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,4 +2,5 @@ export { default as ErrorText } from './ErrorText'
22
export { default as Field } from './Field'
33
export { default as HelpText } from './HelpText'
44
export { default as Label } from './Label'
5+
export { default as Select } from './Select'
56
export { default as TextInput } from './TextInput'

src/components/form/readme.md

Lines changed: 17 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,5 @@
11
### Text Inputs
22

3-
Example:
4-
53
```jsx
64
<Field hasHelp>
75
<Label>Password</Label>
@@ -31,3 +29,20 @@ Validation error example:
3129
<ErrorText>Please enter a valid username</ErrorText>
3230
</Field>
3331
```
32+
33+
### Select
34+
35+
```jsx
36+
<Field>
37+
<Label>Favourite Ninja Turtle</Label>
38+
<Select
39+
name="select"
40+
options={[
41+
{ value: 'leo', label: 'Leonardo' },
42+
{ value: 'mike', label: 'Michelangelo' },
43+
{ value: 'don', label: 'Donatello' },
44+
{ value: 'raph', label: 'Raphael' },
45+
]}
46+
/>
47+
</Field>
48+
```

0 commit comments

Comments
 (0)