@pie-lib/config-ui 12.0.0-beta.5 → 12.0.0-next.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (116) hide show
  1. package/CHANGELOG.json +8 -1653
  2. package/CHANGELOG.md +345 -4
  3. package/LICENSE.md +5 -0
  4. package/NEXT.CHANGELOG.json +1 -0
  5. package/lib/alert-dialog.js +40 -10
  6. package/lib/alert-dialog.js.map +1 -1
  7. package/lib/checkbox.js +58 -48
  8. package/lib/checkbox.js.map +1 -1
  9. package/lib/choice-configuration/feedback-menu.js +24 -26
  10. package/lib/choice-configuration/feedback-menu.js.map +1 -1
  11. package/lib/choice-configuration/index.js +182 -185
  12. package/lib/choice-configuration/index.js.map +1 -1
  13. package/lib/choice-utils.js +5 -7
  14. package/lib/choice-utils.js.map +1 -1
  15. package/lib/feedback-config/feedback-selector.js +69 -73
  16. package/lib/feedback-config/feedback-selector.js.map +1 -1
  17. package/lib/feedback-config/group.js +22 -25
  18. package/lib/feedback-config/group.js.map +1 -1
  19. package/lib/feedback-config/index.js +41 -44
  20. package/lib/feedback-config/index.js.map +1 -1
  21. package/lib/form-section.js +31 -25
  22. package/lib/form-section.js.map +1 -1
  23. package/lib/help.js +37 -47
  24. package/lib/help.js.map +1 -1
  25. package/lib/index.js +1 -2
  26. package/lib/index.js.map +1 -1
  27. package/lib/input.js +12 -17
  28. package/lib/input.js.map +1 -1
  29. package/lib/inputs.js +58 -67
  30. package/lib/inputs.js.map +1 -1
  31. package/lib/langs.js +56 -70
  32. package/lib/langs.js.map +1 -1
  33. package/lib/layout/config-layout.js +78 -47
  34. package/lib/layout/config-layout.js.map +1 -1
  35. package/lib/layout/index.js.map +1 -1
  36. package/lib/layout/layout-contents.js +58 -60
  37. package/lib/layout/layout-contents.js.map +1 -1
  38. package/lib/layout/settings-box.js +25 -33
  39. package/lib/layout/settings-box.js.map +1 -1
  40. package/lib/mui-box/index.js +41 -50
  41. package/lib/mui-box/index.js.map +1 -1
  42. package/lib/number-text-field-custom.js +151 -89
  43. package/lib/number-text-field-custom.js.map +1 -1
  44. package/lib/number-text-field.js +74 -63
  45. package/lib/number-text-field.js.map +1 -1
  46. package/lib/radio-with-label.js +30 -16
  47. package/lib/radio-with-label.js.map +1 -1
  48. package/lib/settings/display-size.js +16 -20
  49. package/lib/settings/display-size.js.map +1 -1
  50. package/lib/settings/index.js +13 -19
  51. package/lib/settings/index.js.map +1 -1
  52. package/lib/settings/panel.js +140 -141
  53. package/lib/settings/panel.js.map +1 -1
  54. package/lib/settings/settings-radio-label.js +29 -16
  55. package/lib/settings/settings-radio-label.js.map +1 -1
  56. package/lib/settings/toggle.js +39 -25
  57. package/lib/settings/toggle.js.map +1 -1
  58. package/lib/tabs/index.js +18 -30
  59. package/lib/tabs/index.js.map +1 -1
  60. package/lib/tags-input/index.js +49 -61
  61. package/lib/tags-input/index.js.map +1 -1
  62. package/lib/two-choice.js +33 -43
  63. package/lib/two-choice.js.map +1 -1
  64. package/lib/with-stateful-model.js +8 -12
  65. package/lib/with-stateful-model.js.map +1 -1
  66. package/package.json +22 -11
  67. package/src/__tests__/alert-dialog.test.jsx +283 -0
  68. package/src/__tests__/checkbox.test.jsx +249 -0
  69. package/src/__tests__/choice-utils.test.js +12 -0
  70. package/src/__tests__/form-section.test.jsx +334 -0
  71. package/src/__tests__/help.test.jsx +184 -0
  72. package/src/__tests__/input.test.jsx +192 -0
  73. package/src/__tests__/langs.test.jsx +457 -0
  74. package/src/__tests__/number-text-field-custom.test.jsx +438 -0
  75. package/src/__tests__/number-text-field.test.jsx +341 -0
  76. package/src/__tests__/radio-with-label.test.jsx +259 -0
  77. package/src/__tests__/settings-panel.test.js +187 -0
  78. package/src/__tests__/settings.test.jsx +515 -0
  79. package/src/__tests__/tabs.test.jsx +193 -0
  80. package/src/__tests__/two-choice.test.js +110 -0
  81. package/src/__tests__/with-stateful-model.test.jsx +145 -0
  82. package/src/alert-dialog.jsx +30 -8
  83. package/src/checkbox.jsx +43 -37
  84. package/src/choice-configuration/__tests__/feedback-menu.test.jsx +163 -0
  85. package/src/choice-configuration/__tests__/index.test.jsx +234 -0
  86. package/src/choice-configuration/feedback-menu.jsx +6 -6
  87. package/src/choice-configuration/index.jsx +208 -192
  88. package/src/feedback-config/__tests__/feedback-config.test.jsx +141 -0
  89. package/src/feedback-config/__tests__/feedback-selector.test.jsx +107 -0
  90. package/src/feedback-config/feedback-selector.jsx +52 -53
  91. package/src/feedback-config/group.jsx +21 -22
  92. package/src/feedback-config/index.jsx +27 -29
  93. package/src/form-section.jsx +26 -18
  94. package/src/help.jsx +20 -28
  95. package/src/input.jsx +1 -1
  96. package/src/inputs.jsx +35 -44
  97. package/src/langs.jsx +41 -46
  98. package/src/layout/__tests__/config.layout.test.jsx +59 -0
  99. package/src/layout/__tests__/layout-content.test.jsx +3 -0
  100. package/src/layout/config-layout.jsx +53 -23
  101. package/src/layout/layout-contents.jsx +38 -40
  102. package/src/layout/settings-box.jsx +16 -19
  103. package/src/mui-box/index.jsx +35 -43
  104. package/src/number-text-field-custom.jsx +117 -65
  105. package/src/number-text-field.jsx +51 -34
  106. package/src/radio-with-label.jsx +26 -10
  107. package/src/settings/display-size.jsx +12 -11
  108. package/src/settings/index.js +2 -1
  109. package/src/settings/panel.jsx +101 -92
  110. package/src/settings/settings-radio-label.jsx +26 -10
  111. package/src/settings/toggle.jsx +37 -18
  112. package/src/tabs/index.jsx +8 -8
  113. package/src/tags-input/__tests__/index.test.jsx +113 -0
  114. package/src/tags-input/index.jsx +35 -38
  115. package/src/two-choice.jsx +15 -19
  116. package/README.md +0 -12
@@ -1,19 +1,19 @@
1
1
  import React from 'react';
2
2
  import PropTypes from 'prop-types';
3
- import { withStyles } from '@material-ui/core/styles';
4
3
  import get from 'lodash/get';
5
4
  import set from 'lodash/set';
6
- import Select from '@material-ui/core/Select';
7
- import Input from '@material-ui/core/Input';
8
- import MenuItem from '@material-ui/core/MenuItem';
5
+ import { styled } from '@mui/material/styles';
6
+ import Select from '@mui/material/Select';
7
+ import Input from '@mui/material/Input';
8
+ import MenuItem from '@mui/material/MenuItem';
9
+ import Typography from '@mui/material/Typography';
9
10
  import debug from 'debug';
10
11
 
11
12
  import Toggle from './toggle';
12
13
  import { NChoice } from '../two-choice';
13
14
  import SettingsRadioLabel from './settings-radio-label';
14
- import { NumberTextField } from '../index';
15
+ import NumberTextField from '../number-text-field';
15
16
  import Checkbox from '../checkbox';
16
- import Typography from '@material-ui/core/Typography';
17
17
 
18
18
  const log = debug('pie-lib:config-ui:settings:panel');
19
19
 
@@ -46,10 +46,24 @@ CheckboxChoice.propTypes = {
46
46
  onChange: PropTypes.func,
47
47
  };
48
48
 
49
- const Radio = ({ classes, label, value, onChange, choices }) => {
49
+ const StyledNChoice = styled(NChoice)(({ theme }) => ({
50
+ marginTop: theme.spacing(0.5),
51
+ paddingBottom: theme.spacing(0.5),
52
+ width: '100%',
53
+ '& > label': {
54
+ color: 'rgba(0, 0, 0, 0.89)',
55
+ transform: 'translate(0, 10px) scale(1)',
56
+ fontSize: '18px',
57
+ marginTop: theme.spacing(2.5),
58
+ },
59
+ '& > div': {
60
+ marginTop: theme.spacing(2.5),
61
+ },
62
+ }));
63
+
64
+ const Radio = ({ label, value, onChange, choices }) => {
50
65
  return (
51
- <NChoice
52
- className={classes.radioSettings}
66
+ <StyledNChoice
53
67
  direction="horizontal"
54
68
  customLabel={SettingsRadioLabel}
55
69
  value={value}
@@ -62,102 +76,85 @@ const Radio = ({ classes, label, value, onChange, choices }) => {
62
76
 
63
77
  Radio.propTypes = { ...baseTypes, choices: PropTypes.arrayOf(PropTypes.shape(labelValue)) };
64
78
 
65
- const StyledRadio = withStyles((theme) => ({
66
- radioSettings: {
67
- marginTop: theme.spacing.unit / 2,
68
- paddingBottom: theme.spacing.unit / 2,
69
- width: '100%',
70
- '& > label': {
71
- color: 'rgba(0, 0, 0, 0.89)',
72
- transform: 'translate(0, 10px) scale(1)',
73
- fontSize: '14px',
74
- },
75
- '& > div': {
76
- marginTop: theme.spacing.unit * 2.5,
77
- },
78
- },
79
- label: {
80
- display: 'none',
81
- },
82
- }))(Radio);
79
+ const StyledRadio = Radio;
83
80
 
84
- const Dropdown = withStyles((theme) => ({
85
- label: {
86
- margin: 0,
87
- fontSize: theme.typography.fontSize,
88
- },
89
- wrapper: {
90
- marginTop: theme.spacing.unit / 2,
91
- border: '2px solid lightgrey',
92
- borderRadius: '4px',
93
- padding: `0 ${theme.spacing.unit}px`,
94
- },
95
- }))(({ classes, label, value, onChange, choices = [] }) => {
81
+ const StyledLabel = styled('p')(({ theme }) => ({
82
+ margin: 0,
83
+ fontSize: theme.typography.fontSize,
84
+ }));
85
+
86
+ const StyledSelect = styled(Select)(({ theme }) => ({
87
+ marginTop: theme.spacing(0.5),
88
+ border: '2px solid lightgrey',
89
+ padding: `0 ${theme.spacing(1)}`,
90
+ borderRadius: '4px',
91
+ }));
92
+
93
+ const Dropdown = ({ label, value, onChange, choices = [] }) => {
96
94
  const getItemLabel = (l) => (typeof l === 'string' ? l : l.label);
97
95
  const getItemValue = (l) => (typeof l === 'string' ? l : l.value);
98
96
  return (
99
97
  <div>
100
- {label && <p className={classes.label}>{label}</p>}
101
- <Select
102
- className={classes.wrapper}
98
+ {label && <StyledLabel>{label}</StyledLabel>}
99
+ <StyledSelect
103
100
  value={value || (choices && choices[0])}
104
101
  onChange={({ target }) => onChange(target.value)}
105
102
  input={<Input id={`dropdown-${label}`} />}
106
103
  disableUnderline
104
+ MenuProps={{ transitionDuration: { enter: 225, exit: 195 } }}
107
105
  >
108
106
  {choices.map((l, index) => (
109
107
  <MenuItem key={index} value={getItemValue(l)}>
110
108
  {getItemLabel(l)}
111
109
  </MenuItem>
112
110
  ))}
113
- </Select>
111
+ </StyledSelect>
114
112
  </div>
115
113
  );
116
- });
114
+ };
117
115
 
118
116
  Dropdown.propTypes = { ...baseTypes, choices: PropTypes.arrayOf(PropTypes.string) };
119
117
 
120
- const TextField = withStyles((theme) => ({
121
- field: {
122
- marginRight: theme.spacing.unit * 3,
123
- marginTop: theme.spacing.unit,
124
- },
125
- }))(({ classes, label }) => {
126
- return <Typography className={classes.field}>{label}</Typography>;
127
- });
128
-
129
- const NumberField = withStyles((theme) => ({
130
- field: {
131
- width: '35%',
132
- marginRight: theme.spacing.unit * 3,
133
- marginTop: theme.spacing.unit,
134
- },
135
- wrapper: {
136
- marginTop: theme.spacing.unit / 2,
118
+ const StyledTypography = styled(Typography)(({ theme }) => ({
119
+ marginRight: theme.spacing(3),
120
+ marginTop: theme.spacing(1),
121
+ }));
122
+
123
+ const TextField = ({ label }) => {
124
+ return <StyledTypography>{label}</StyledTypography>;
125
+ };
126
+
127
+ const StyledNumberTextField = styled(NumberTextField)(({ theme }) => ({
128
+ width: '35%',
129
+ marginRight: theme.spacing(3),
130
+ marginTop: theme.spacing(1),
131
+ '& .MuiInputBase-root': {
132
+ marginTop: theme.spacing(0.5),
137
133
  border: '2px solid lightgrey',
138
134
  borderRadius: '4px',
139
- padding: `0 ${theme.spacing.unit}px`,
135
+ padding: `0 ${theme.spacing(1)}`,
136
+ backgroundColor: 'transparent',
140
137
  },
141
- }))(({ classes, label, value, onChange = () => {}, suffix, min, max }) => {
138
+ }));
139
+
140
+ const NumberField = ({ label, value, onChange = () => { }, suffix, min, max }) => {
142
141
  return (
143
- <NumberTextField
142
+ <StyledNumberTextField
143
+ variant={'standard'}
144
144
  label={label || 'Label'}
145
145
  value={value}
146
146
  max={max}
147
147
  min={min}
148
148
  onChange={(ev, value) => onChange(value)}
149
149
  suffix={suffix}
150
- className={classes.field}
151
150
  showErrorWhenOutsideRange
152
- inputClassName={classes.wrapper}
153
151
  disableUnderline
154
152
  />
155
153
  );
156
- });
154
+ };
157
155
 
158
156
  NumberField.propTypes = {
159
157
  ...baseTypes,
160
- classes: PropTypes.object,
161
158
  suffix: PropTypes.string,
162
159
  min: PropTypes.number,
163
160
  max: PropTypes.number,
@@ -168,7 +165,9 @@ TextField.propTypes = {
168
165
  ...baseTypes,
169
166
  };
170
167
 
171
- const ToggleWrapper = ({ label, value, onChange }) => <Toggle label={label} checked={!!value} toggle={onChange} />;
168
+ const ToggleWrapper = ({ disabled, label, value, onChange }) => (
169
+ <Toggle label={label} checked={!!value} disabled={!!disabled} toggle={onChange} />
170
+ );
172
171
 
173
172
  ToggleWrapper.propTypes = { ...baseTypes, value: PropTypes.bool };
174
173
 
@@ -181,22 +180,24 @@ const tagMap = {
181
180
  textField: TextField,
182
181
  };
183
182
 
184
- const Group = withStyles((theme) => ({
185
- group: {
186
- margin: `0 0 ${theme.spacing.unit * 2}px 0`,
187
- },
188
- groupHeader: {
189
- color: '#495B8F',
190
- fontSize: theme.typography.fontSize + 2,
191
- fontWeight: 600,
192
- marginBottom: theme.spacing.unit,
193
- },
194
- numberFields: {
195
- fontSize: theme.typography.fontSize,
196
- marginBottom: 0,
197
- },
198
- }))((props) => {
199
- const { classes, model, label, group, configuration, onChange } = props;
183
+ const StyledGroup = styled('div')(({ theme }) => ({
184
+ margin: `0 0 ${theme.spacing(2)} 0`,
185
+ }));
186
+
187
+ const StyledGroupHeader = styled('div')(({ theme }) => ({
188
+ color: '#495B8F',
189
+ fontSize: theme.typography.fontSize + 2,
190
+ fontWeight: 600,
191
+ marginBottom: theme.spacing(1),
192
+ }));
193
+
194
+ const StyledNumberFields = styled('p')(({ theme }) => ({
195
+ fontSize: theme.typography.fontSize,
196
+ marginBottom: 0,
197
+ }));
198
+
199
+ const Group = (props) => {
200
+ const { model, label, group, configuration, onChange } = props;
200
201
 
201
202
  /**
202
203
  * @param group - the group of settings
@@ -224,7 +225,7 @@ const Group = withStyles((theme) => ({
224
225
  if (type === 'numberFields') {
225
226
  return (
226
227
  <div key={`numberField-${label}`}>
227
- <p className={classes.numberFields}>{label}</p>
228
+ <StyledNumberFields>{label}</StyledNumberFields>
228
229
  {Object.keys(fields).map((fieldKey) => {
229
230
  return getTag(group, `${key}.${fieldKey}`, `${key}.fields.${fieldKey}`);
230
231
  })}
@@ -248,15 +249,23 @@ const Group = withStyles((theme) => ({
248
249
  };
249
250
 
250
251
  return (
251
- <div className={classes.group}>
252
- <div className={classes.groupHeader}>{label}</div>
252
+ <StyledGroup>
253
+ <StyledGroupHeader>{label}</StyledGroupHeader>
253
254
 
254
255
  {Object.keys(group).map((key) => {
255
256
  return content(group, key);
256
257
  })}
257
- </div>
258
+ </StyledGroup>
258
259
  );
259
- });
260
+ };
261
+
262
+ Group.propTypes = {
263
+ model: PropTypes.object,
264
+ label: PropTypes.string,
265
+ group: PropTypes.object,
266
+ configuration: PropTypes.object,
267
+ onChange: PropTypes.func,
268
+ };
260
269
 
261
270
  export class Panel extends React.Component {
262
271
  static propTypes = {
@@ -269,8 +278,8 @@ export class Panel extends React.Component {
269
278
  };
270
279
 
271
280
  static defaultProps = {
272
- onChangeModel: () => {},
273
- onChangeConfiguration: () => {},
281
+ onChangeModel: () => { },
282
+ onChangeConfiguration: () => { },
274
283
  };
275
284
 
276
285
  change = (key, value, isConfigProperty = false) => {
@@ -1,20 +1,36 @@
1
- import FormControlLabel from '@material-ui/core/FormControlLabel';
2
- import Radio from '@material-ui/core/Radio';
1
+ import FormControlLabel from '@mui/material/FormControlLabel';
2
+ import Radio from '@mui/material/Radio';
3
3
  import React from 'react';
4
- import { withStyles } from '@material-ui/core/styles';
4
+ import PropTypes from 'prop-types';
5
+ import { styled } from '@mui/material/styles';
6
+ import { color } from '@pie-lib/render-ui';
5
7
 
6
- export default withStyles((theme) => ({
7
- label: {
8
+ const StyledFormControlLabel = styled(FormControlLabel)(({ theme }) => ({
9
+ '& .MuiFormControlLabel-label': {
8
10
  color: 'rgba(0, 0, 0, 0.89)',
9
11
  fontSize: theme.typography.fontSize - 2,
10
12
  left: '-5px',
11
13
  position: 'relative',
12
14
  },
13
- }))(({ label, value, checked, onChange, classes }) => (
14
- <FormControlLabel
15
+ }));
16
+
17
+ const StyledRadio = styled(Radio)(() => ({
18
+ color: `${color.tertiary()} !important`,
19
+ }));
20
+
21
+ const SettingsRadioLabel = ({ label, value, checked, onChange }) => (
22
+ <StyledFormControlLabel
15
23
  value={value}
16
- classes={classes}
17
- control={<Radio checked={checked} onChange={onChange} />}
24
+ control={<StyledRadio checked={checked} onChange={onChange} />}
18
25
  label={label}
19
26
  />
20
- ));
27
+ );
28
+
29
+ SettingsRadioLabel.propTypes = {
30
+ label: PropTypes.string,
31
+ value: PropTypes.string,
32
+ checked: PropTypes.bool,
33
+ onChange: PropTypes.func,
34
+ };
35
+
36
+ export default SettingsRadioLabel;
@@ -1,29 +1,48 @@
1
1
  import React from 'react';
2
2
  import PropTypes from 'prop-types';
3
- import InputLabel from '@material-ui/core/InputLabel';
4
- import { withStyles } from '@material-ui/core/styles';
5
- import Switch from '@material-ui/core/Switch';
3
+ import InputLabel from '@mui/material/InputLabel';
4
+ import { styled } from '@mui/material/styles';
5
+ import Switch from '@mui/material/Switch';
6
+ import { color } from '@pie-lib/render-ui';
6
7
 
7
- const Toggle = withStyles((theme) => ({
8
- toggle: {
9
- display: 'flex',
10
- width: '100%',
11
- justifyContent: 'space-between',
8
+ const StyledToggle = styled('div')(() => ({
9
+ display: 'flex',
10
+ width: '100%',
11
+ justifyContent: 'space-between',
12
+ }));
13
+
14
+ const StyledInputLabel = styled(InputLabel)(({ theme }) => ({
15
+ color: 'rgba(0, 0, 0, 0.89)',
16
+ fontSize: theme.typography.fontSize,
17
+ paddingTop: theme.spacing(2),
18
+ }));
19
+
20
+ const StyledSwitch = styled(Switch)(({ checked }) => ({
21
+ '&.Mui-checked .MuiSwitch-thumb': {
22
+ color: `${color.tertiary()} !important`,
12
23
  },
13
- label: {
14
- color: 'rgba(0, 0, 0, 0.89)',
15
- fontSize: theme.typography.fontSize,
16
- paddingTop: theme.spacing.unit * 2,
24
+ '&.Mui-checked .MuiSwitch-track': {
25
+ backgroundColor: `${color.tertiaryLight()} !important`,
17
26
  },
18
- }))(({ checked, label, toggle, classes }) => (
19
- <div className={classes.toggle}>
20
- <InputLabel className={classes.label}>{label}</InputLabel>
21
- <Switch checked={checked} onChange={(e) => toggle(e.target.checked)} />
22
- </div>
23
- ));
27
+ '& .MuiSwitch-track': {
28
+ backgroundColor: checked ? `${color.tertiaryLight()} !important` : undefined,
29
+ },
30
+ }));
31
+
32
+ const Toggle = ({ checked, disabled, label, toggle }) => (
33
+ <StyledToggle>
34
+ <StyledInputLabel>{label}</StyledInputLabel>
35
+ <StyledSwitch
36
+ checked={checked}
37
+ disabled={disabled}
38
+ onChange={(e) => toggle(e.target.checked)}
39
+ />
40
+ </StyledToggle>
41
+ );
24
42
 
25
43
  Toggle.propTypes = {
26
44
  checked: PropTypes.bool,
45
+ disabled: PropTypes.bool,
27
46
  label: PropTypes.string.isRequired,
28
47
  toggle: PropTypes.func.isRequired,
29
48
  };
@@ -1,13 +1,14 @@
1
1
  import React from 'react';
2
2
 
3
- import MuiTabs from '@material-ui/core/Tabs';
4
- import MuiTab from '@material-ui/core/Tab';
3
+ import MuiTabs from '@mui/material/Tabs';
4
+ import MuiTab from '@mui/material/Tab';
5
5
  import PropTypes from 'prop-types';
6
- import { withStyles } from '@material-ui/core';
6
+ import { styled } from '@mui/material/styles';
7
+
8
+ const StyledMuiTab = styled(MuiTab)(() => ({}));
7
9
 
8
10
  export class Tabs extends React.Component {
9
11
  static propTypes = {
10
- classes: PropTypes.object,
11
12
  className: PropTypes.string,
12
13
  contentClassName: PropTypes.string,
13
14
  contentStyle: PropTypes.object,
@@ -25,14 +26,13 @@ export class Tabs extends React.Component {
25
26
 
26
27
  render() {
27
28
  const { value } = this.state;
28
- const { children, className, contentClassName, contentStyle = {}, classes } = this.props;
29
- const tabClasses = { root: classes.tabRoot };
29
+ const { children, className, contentClassName, contentStyle = {} } = this.props;
30
30
 
31
31
  return (
32
32
  <div className={className}>
33
33
  <MuiTabs indicatorColor="primary" value={value} onChange={this.handleChange}>
34
34
  {React.Children.map(children, (c, index) =>
35
- c && c.props.title ? <MuiTab classes={tabClasses} key={index} label={c.props.title} /> : null,
35
+ c && c.props.title ? <StyledMuiTab key={index} label={c.props.title} /> : null,
36
36
  )}
37
37
  </MuiTabs>
38
38
 
@@ -44,4 +44,4 @@ export class Tabs extends React.Component {
44
44
  }
45
45
  }
46
46
 
47
- export default withStyles(() => ({ tabRoot: {} }))(Tabs);
47
+ export default Tabs;
@@ -0,0 +1,113 @@
1
+ import { TagsInput } from '../index';
2
+ import { render, screen, userEvent, pressKey, Keys } from '@pie-lib/test-utils';
3
+ import React from 'react';
4
+
5
+ describe('TagsInput', () => {
6
+ describe('rendering', () => {
7
+ it('renders existing tags as chips', () => {
8
+ render(<TagsInput tags={['foo', 'bar']} onChange={jest.fn()} />);
9
+
10
+ expect(screen.getByText('foo')).toBeInTheDocument();
11
+ expect(screen.getByText('bar')).toBeInTheDocument();
12
+ });
13
+
14
+ it('renders input field', () => {
15
+ render(<TagsInput tags={['foo']} onChange={jest.fn()} />);
16
+
17
+ const input = screen.getByRole('textbox');
18
+ expect(input).toBeInTheDocument();
19
+ });
20
+ });
21
+
22
+ describe('user interactions', () => {
23
+ let onChange;
24
+ const renderComponent = (tags = ['foo']) => {
25
+ onChange = jest.fn();
26
+ return render(<TagsInput onChange={onChange} tags={tags} />);
27
+ };
28
+
29
+ describe('focus behavior', () => {
30
+ it('allows user to focus the input', async () => {
31
+ const user = userEvent.setup();
32
+ renderComponent();
33
+
34
+ const input = screen.getByRole('textbox');
35
+ await user.click(input);
36
+
37
+ expect(input).toHaveFocus();
38
+ });
39
+
40
+ it('allows user to blur the input', async () => {
41
+ const user = userEvent.setup();
42
+ renderComponent();
43
+
44
+ const input = screen.getByRole('textbox');
45
+ await user.click(input);
46
+ expect(input).toHaveFocus();
47
+
48
+ await user.tab();
49
+ expect(input).not.toHaveFocus();
50
+ });
51
+ });
52
+
53
+ describe('typing in input', () => {
54
+ it('updates input value when user types', async () => {
55
+ const user = userEvent.setup();
56
+ renderComponent();
57
+
58
+ const input = screen.getByRole('textbox');
59
+ await user.type(input, 'boo');
60
+
61
+ expect(input).toHaveValue('boo');
62
+ });
63
+ });
64
+
65
+ describe('adding tags', () => {
66
+ it('adds new tag when user presses Enter', async () => {
67
+ const user = userEvent.setup();
68
+ renderComponent();
69
+
70
+ const input = screen.getByRole('textbox');
71
+ await user.type(input, 'banana');
72
+ pressKey(input, Keys.ENTER);
73
+
74
+ expect(onChange).toHaveBeenCalledWith(['foo', 'banana']);
75
+ });
76
+
77
+ it('does not add duplicate tags', async () => {
78
+ const user = userEvent.setup();
79
+ renderComponent();
80
+
81
+ const input = screen.getByRole('textbox');
82
+ await user.type(input, 'foo');
83
+ pressKey(input, Keys.ENTER);
84
+
85
+ expect(onChange).not.toHaveBeenCalled();
86
+ });
87
+
88
+ it('clears input after adding tag', async () => {
89
+ const user = userEvent.setup();
90
+ renderComponent();
91
+
92
+ const input = screen.getByRole('textbox');
93
+ await user.type(input, 'banana');
94
+ pressKey(input, Keys.ENTER);
95
+
96
+ expect(input).toHaveValue('');
97
+ });
98
+ });
99
+
100
+ describe('deleting tags', () => {
101
+ it('removes tag when user clicks delete button', async () => {
102
+ const user = userEvent.setup();
103
+ renderComponent(['foo', 'bar']);
104
+
105
+ // Find the delete button for 'foo' tag
106
+ const deleteButtons = screen.getAllByTestId('CancelIcon');
107
+ await user.click(deleteButtons[0]);
108
+
109
+ expect(onChange).toHaveBeenCalledWith(['bar']);
110
+ });
111
+ });
112
+ });
113
+ });
@@ -1,27 +1,48 @@
1
1
  import React from 'react';
2
2
  import PropTypes from 'prop-types';
3
- import { withStyles } from '@material-ui/core/styles';
3
+ import { styled } from '@mui/material/styles';
4
4
  import uniq from 'lodash/uniq';
5
- import Chip from '@material-ui/core/Chip';
5
+ import Chip from '@mui/material/Chip';
6
6
  import MuiBox from '../mui-box';
7
7
 
8
8
  const ENTER = 13;
9
9
 
10
- const Tag = withStyles(() => ({
11
- tag: {
12
- padding: '0px',
13
- margin: '1px',
14
- },
15
- }))(({ classes, label, onDelete }) => <Chip className={classes.tag} label={label} onDelete={onDelete} />);
10
+ const StyledChip = styled(Chip)(() => ({
11
+ padding: '0px',
12
+ margin: '1px',
13
+ }));
14
+
15
+ const Tag = ({ label, onDelete }) => <StyledChip label={label} onDelete={onDelete} />;
16
16
 
17
17
  Tag.propTypes = {
18
18
  label: PropTypes.string.isRequired,
19
19
  onDelete: PropTypes.func.isRequired,
20
20
  };
21
21
 
22
+ const StyledTagsInput = styled('div')(({ theme }) => ({
23
+ border: `0px solid ${theme.palette.background.paper}`,
24
+ display: 'flex',
25
+ flexWrap: 'wrap',
26
+ }));
27
+
28
+ const StyledInput = styled('input')(({ theme }) => ({
29
+ padding: '2px',
30
+ margin: '1px',
31
+ minWidth: '30px',
32
+ width: '100%',
33
+ flex: '1',
34
+ border: `0px solid ${theme.palette.background.paper}`,
35
+ height: '28px',
36
+ fontSize: theme.typography.fontSize,
37
+ fontFamily: theme.typography.fontFamily,
38
+ outline: 'none',
39
+ '&:focus': {
40
+ outline: 'none',
41
+ },
42
+ }));
43
+
22
44
  export class TagsInput extends React.Component {
23
45
  static propTypes = {
24
- classes: PropTypes.object.isRequired,
25
46
  tags: PropTypes.arrayOf(PropTypes.string).isRequired,
26
47
  onChange: PropTypes.func.isRequired,
27
48
  };
@@ -70,50 +91,26 @@ export class TagsInput extends React.Component {
70
91
  };
71
92
 
72
93
  render() {
73
- const { classes, tags } = this.props;
94
+ const { tags } = this.props;
74
95
  return (
75
96
  <MuiBox focused={this.state.focused}>
76
- <div className={classes.tagsInput}>
97
+ <StyledTagsInput>
77
98
  {(tags || []).map((t, index) => (
78
99
  <Tag key={index} label={t} onDelete={() => this.deleteTag(t)} />
79
100
  ))}
80
- <input
101
+ <StyledInput
81
102
  ref={(r) => (this.input = r)}
82
103
  onKeyDown={this.onKeyDown}
83
104
  onChange={this.onChange}
84
- className={classes.input}
85
105
  value={this.state.value}
86
106
  onFocus={this.onFocus}
87
107
  onBlur={this.onBlur}
88
108
  type="text"
89
109
  />
90
- </div>
110
+ </StyledTagsInput>
91
111
  </MuiBox>
92
112
  );
93
113
  }
94
114
  }
95
115
 
96
- const styles = (theme) => ({
97
- tagsInput: {
98
- border: `0px solid ${theme.palette.background.paper}`,
99
- display: 'flex',
100
- flexWrap: 'wrap',
101
- },
102
- input: {
103
- padding: '2px',
104
- margin: '1px',
105
- minWidth: '30px',
106
- width: '100%',
107
- flex: '1',
108
- border: `0px solid ${theme.palette.background.paper}`,
109
- height: '28px',
110
- fontSize: theme.typography.fontSize,
111
- fontFamily: theme.typography.fontFamily,
112
- outline: 'none',
113
- '&:focus': {
114
- outline: 'none',
115
- },
116
- },
117
- });
118
-
119
- export default withStyles(styles)(TagsInput);
116
+ export default TagsInput;