Skip to content

Commit 20a0030

Browse files
committed
feat(TextField): Use BottomDrawer on mobile for Select input
`options` must be passed to "activate" the feature
1 parent f14f998 commit 20a0030

File tree

3 files changed

+142
-2
lines changed

3 files changed

+142
-2
lines changed

react/TextField/MobileSelect.jsx

Lines changed: 125 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,125 @@
1+
import React, { forwardRef, useEffect, useState } from 'react'
2+
import MuiTextField from '@material-ui/core/TextField'
3+
import PropTypes from 'prop-types'
4+
5+
import Icon from '../Icon'
6+
import BottomIcon from '../Icons/Bottom'
7+
import InputAdornment from '../InputAdornment'
8+
import ListItemText from '../ListItemText'
9+
import ListItemIcon from '../ListItemIcon'
10+
import Radio from '../Radios'
11+
import ActionsMenuWrapper from '../ActionsMenu/ActionsMenuWrapper'
12+
import ActionsMenuItem from '../ActionsMenu/ActionsMenuItem'
13+
14+
const MobileSelect = forwardRef(
15+
(
16+
{ name, options, disabled, value, children, onClick, onChange, ...props },
17+
ref
18+
) => {
19+
// As they are controlled input, we have to set empty string as default
20+
// because values can't be undefined and then defined
21+
const [state, setState] = useState({ label: '', value: '' })
22+
const [showDrawer, setShowDrawer] = useState(false)
23+
24+
const initialLabel = options.find(option => {
25+
return option.value === value
26+
})?.label
27+
28+
const handleClick = () => {
29+
setShowDrawer(true)
30+
onClick?.()
31+
}
32+
33+
const handleItemClick = ({ value, children, onClick }) => {
34+
if (onClick) {
35+
return onClick()
36+
}
37+
setState({ label: children, value })
38+
onChange?.({ target: { value } })
39+
}
40+
41+
const handleClose = () => {
42+
setShowDrawer(false)
43+
}
44+
45+
useEffect(() => {
46+
setState({ label: initialLabel || '', value: value || '' })
47+
}, [initialLabel, value])
48+
49+
return (
50+
<>
51+
<MuiTextField
52+
style={{ display: 'none' }}
53+
type="hidden"
54+
name={name}
55+
value={state.value}
56+
/>
57+
<MuiTextField
58+
{...props}
59+
ref={ref}
60+
type="button"
61+
aria-controls="simple-menu"
62+
aria-haspopup="true"
63+
name="dummy"
64+
value={state.label}
65+
disabled={disabled}
66+
inputProps={{ className: 'u-ta-left' }}
67+
InputProps={{
68+
endAdornment: (
69+
<InputAdornment position="end">
70+
<Icon
71+
icon={BottomIcon}
72+
color={
73+
disabled
74+
? 'var(--disabledTextColor)'
75+
: 'var(--iconTextColor)'
76+
}
77+
/>
78+
</InputAdornment>
79+
)
80+
}}
81+
onClick={handleClick}
82+
/>
83+
84+
{showDrawer && (
85+
<ActionsMenuWrapper open autoClose onClose={handleClose}>
86+
{React.Children.map(children, child => {
87+
return React.isValidElement(child) ? (
88+
<ActionsMenuItem
89+
{...child.props}
90+
size="small"
91+
autoFocus={child.props.value === value}
92+
onClick={() => handleItemClick(child.props)}
93+
>
94+
<ListItemIcon>
95+
<Radio
96+
aria-hidden="true"
97+
tabIndex="-1"
98+
checked={child.props.value === state.value}
99+
/>
100+
</ListItemIcon>
101+
<ListItemText primary={child.props.children} />
102+
</ActionsMenuItem>
103+
) : null
104+
})}
105+
</ActionsMenuWrapper>
106+
)}
107+
</>
108+
)
109+
}
110+
)
111+
112+
MobileSelect.propTypes = {
113+
name: PropTypes.string,
114+
options: PropTypes.array,
115+
disabled: PropTypes.bool,
116+
value: PropTypes.string,
117+
children: PropTypes.oneOfType([
118+
PropTypes.node,
119+
PropTypes.arrayOf(PropTypes.node)
120+
]),
121+
onClick: PropTypes.func,
122+
onChange: PropTypes.func
123+
}
124+
125+
export default MobileSelect

react/TextField/Readme.md

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -174,6 +174,7 @@ const handleChange = (event) => {
174174
<div className="u-mb-1 u-mt-2">Select</div>
175175
<TextField
176176
select
177+
options={options}
177178
value={state.option}
178179
error={variant.error}
179180
size={variant.small ? 'small' : 'medium'}
@@ -192,6 +193,7 @@ const handleChange = (event) => {
192193
<TextField
193194
className="u-ml-1"
194195
select
196+
options={options}
195197
value={state.option}
196198
disabled
197199
error={variant.error}

react/TextField/index.jsx

Lines changed: 15 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -4,15 +4,29 @@ import MuiTextField from '@material-ui/core/TextField'
44
import Icon from '../Icon'
55
import BottomIcon from '../Icons/Bottom'
66
import { getRandomUUID } from '../helpers/getRandomUUID'
7+
import { useBreakpoints } from '../providers/Breakpoints'
8+
import MobileSelect from './MobileSelect'
79

8-
const TextField = forwardRef(({ children, ...props }, ref) => {
10+
const TextField = forwardRef(({ select, options, children, ...props }, ref) => {
911
// A11Y, https://v4.mui.com/api/text-field/#props
1012
const uuid = getRandomUUID()
13+
const { isMobile } = useBreakpoints()
14+
15+
// options is not required to avoid breaking change but needed to have the mobile behavior
16+
if (isMobile && select && options) {
17+
return (
18+
<MobileSelect {...props} ref={ref} id={uuid} options={options}>
19+
{children}
20+
</MobileSelect>
21+
)
22+
}
1123

1224
return (
1325
<MuiTextField
26+
{...props}
1427
ref={ref}
1528
id={uuid}
29+
select={select}
1630
SelectProps={{
1731
IconComponent: iconProps => (
1832
<Icon
@@ -26,7 +40,6 @@ const TextField = forwardRef(({ children, ...props }, ref) => {
2640
/>
2741
)
2842
}}
29-
{...props}
3043
>
3144
{children}
3245
</MuiTextField>

0 commit comments

Comments
 (0)