@pautena/react-design-system 0.2.0 → 0.3.1

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 (65) hide show
  1. package/dist/cjs/index.js +13 -4
  2. package/dist/cjs/index.js.map +1 -1
  3. package/dist/cjs/types/components/value-displays/index.d.ts +1 -0
  4. package/dist/cjs/types/components/value-displays/value-datetime/index.d.ts +1 -0
  5. package/dist/cjs/types/components/value-displays/value-datetime/value-datetime.d.ts +18 -0
  6. package/dist/cjs/types/generators/generators.mock.d.ts +9 -5
  7. package/dist/cjs/types/generators/generators.model.d.ts +25 -1
  8. package/dist/cjs/types/generators/model-router/index.d.ts +1 -0
  9. package/dist/cjs/types/generators/model-router/model-router.types.d.ts +1 -0
  10. package/dist/cjs/types/generators/model-router/screens/details-screen.d.ts +1 -1
  11. package/dist/cjs/types/hooks/index.d.ts +1 -0
  12. package/dist/cjs/types/hooks/routing/index.d.ts +1 -0
  13. package/dist/cjs/types/hooks/routing/routing.hooks.d.ts +5 -0
  14. package/dist/cjs/types/providers/notification-center/index.d.ts +1 -0
  15. package/dist/cjs/types/providers/notification-center/notification-center.hooks.d.ts +6 -0
  16. package/dist/esm/index.js +13 -4
  17. package/dist/esm/index.js.map +1 -1
  18. package/dist/esm/types/components/value-displays/index.d.ts +1 -0
  19. package/dist/esm/types/components/value-displays/value-datetime/index.d.ts +1 -0
  20. package/dist/esm/types/components/value-displays/value-datetime/value-datetime.d.ts +18 -0
  21. package/dist/esm/types/generators/generators.mock.d.ts +9 -5
  22. package/dist/esm/types/generators/generators.model.d.ts +25 -1
  23. package/dist/esm/types/generators/model-router/index.d.ts +1 -0
  24. package/dist/esm/types/generators/model-router/model-router.types.d.ts +1 -0
  25. package/dist/esm/types/generators/model-router/screens/details-screen.d.ts +1 -1
  26. package/dist/esm/types/hooks/index.d.ts +1 -0
  27. package/dist/esm/types/hooks/routing/index.d.ts +1 -0
  28. package/dist/esm/types/hooks/routing/routing.hooks.d.ts +5 -0
  29. package/dist/esm/types/providers/notification-center/index.d.ts +1 -0
  30. package/dist/esm/types/providers/notification-center/notification-center.hooks.d.ts +6 -0
  31. package/dist/index.d.ts +54 -2
  32. package/package.json +6 -2
  33. package/src/components/value-displays/index.ts +1 -0
  34. package/src/components/value-displays/value-datetime/index.ts +1 -0
  35. package/src/components/value-displays/value-datetime/value-datetime.stories.tsx +21 -0
  36. package/src/components/value-displays/value-datetime/value-datetime.test.tsx +23 -0
  37. package/src/components/value-displays/value-datetime/value-datetime.tsx +40 -0
  38. package/src/components/value-displays/value-text/{value-test.test.tsx → value-text.test.tsx} +0 -0
  39. package/src/generators/generators.mock.ts +56 -17
  40. package/src/generators/generators.model.ts +39 -1
  41. package/src/generators/model-form/model-form.stories.tsx +2 -2
  42. package/src/generators/model-form/model-form.test.tsx +39 -22
  43. package/src/generators/model-form/model-form.tsx +220 -33
  44. package/src/generators/model-router/index.ts +1 -0
  45. package/src/generators/model-router/model-router.test.tsx +338 -70
  46. package/src/generators/model-router/model-router.tsx +1 -1
  47. package/src/generators/model-router/model-router.types.ts +4 -0
  48. package/src/generators/model-router/screens/add-screen.tsx +16 -20
  49. package/src/generators/model-router/screens/details-screen.tsx +3 -2
  50. package/src/generators/model-router/screens/list-screen.tsx +17 -0
  51. package/src/generators/model-router/screens/update-screen.tsx +22 -13
  52. package/src/generators/model-router/stories/model-router.stories.tsx +54 -3
  53. package/src/generators/object-details/object-details.tsx +5 -4
  54. package/src/hooks/index.ts +1 -0
  55. package/src/hooks/routing/index.ts +1 -0
  56. package/src/hooks/routing/routing.hooks.ts +23 -0
  57. package/src/hooks/routing/routing.test.tsx +83 -0
  58. package/src/providers/notification-center/index.ts +1 -0
  59. package/src/providers/notification-center/notification-center.hooks.ts +23 -0
  60. package/src/providers/notification-center/notification-center.test.tsx +87 -1
  61. package/src/storybook.tsx +10 -0
  62. package/src/tests/actions.ts +43 -0
  63. package/src/tests/assertions.ts +75 -1
  64. package/src/tests/index.ts +1 -0
  65. package/src/tests/testing-library.tsx +5 -1
@@ -5,8 +5,19 @@ import {
5
5
  expectModelFieldInputValue,
6
6
  render,
7
7
  screen,
8
+ selectOption,
9
+ selectOptions,
10
+ pickDatetime,
11
+ expectToHaveBeenCalledOnceWithMockInstance,
8
12
  } from "../../tests";
9
- import { createModelInstance, MockInstance, mockModel } from "../generators.mock";
13
+ import {
14
+ BirthDateFormat,
15
+ createModelInstance,
16
+ MockInstance,
17
+ mockModel,
18
+ ReturnTimeFormat,
19
+ TradeDateFormat,
20
+ } from "../generators.mock";
10
21
  import userEvent from "@testing-library/user-event";
11
22
 
12
23
  describe("ModelForm", () => {
@@ -47,54 +58,60 @@ describe("ModelForm", () => {
47
58
 
48
59
  it("would call onSubmit if I fullfill all inputs and press the submit button", async () => {
49
60
  const { onSubmit } = renderComponent();
61
+ const birthDate = new Date(2047, 11, 26);
62
+ const returnTime = new Date(1970, 0, 1, 10, 12);
63
+ const tradeDate = new Date(2047, 11, 26, 12, 50);
50
64
 
51
65
  await userEvent.type(screen.getByRole("textbox", { name: "Id" }), "Id-1");
52
66
  await userEvent.type(screen.getByRole("textbox", { name: /first name/i }), "Karianne");
53
67
  await userEvent.type(screen.getByRole("textbox", { name: /middle name/i }), "Noah");
54
68
  await userEvent.type(screen.getByRole("textbox", { name: /last name/i }), "Gorczany");
55
- await userEvent.type(screen.getByRole("textbox", { name: /gender/i }), "Cis Man");
69
+ await selectOption(screen.getByRole("button", { name: /gender/i }), "Cis Man");
56
70
  await userEvent.type(screen.getByRole("spinbutton", { name: /age/i }), "37");
57
- await userEvent.type(
58
- screen.getByRole("textbox", { name: /birth date/i }),
59
- "Tue Nov 26 2047 12:14:19",
60
- );
61
- await userEvent.type(screen.getByRole("textbox", { name: /model/i }), "Spyder");
62
- await userEvent.type(screen.getByRole("textbox", { name: /manufacturer/i }), "Bugatti");
71
+ pickDatetime(screen.getByRole("textbox", { name: /birth date/i }), birthDate, BirthDateFormat);
72
+ await selectOption(screen.getByRole("button", { name: /model/i }), "Spyder");
73
+ await selectOption(screen.getByRole("button", { name: /manufacturer/i }), "Bugatti");
63
74
  await userEvent.type(screen.getByRole("textbox", { name: /color/i }), "red");
64
- await userEvent.type(screen.getByRole("textbox", { name: /type/i }), "Convertible");
75
+ await selectOptions(screen.getByRole("button", { name: /type/i }), [
76
+ "Coupe",
77
+ "Hatchback",
78
+ "Minivan",
79
+ ]);
65
80
  await userEvent.type(screen.getByRole("textbox", { name: /vin/i }), "46N6UE4VJ2XL28828");
66
81
  await userEvent.type(screen.getByRole("textbox", { name: /vrm/i }), "NE51AFH");
82
+ pickDatetime(
83
+ screen.getByRole("textbox", { name: /return time/i }),
84
+ returnTime,
85
+ ReturnTimeFormat,
86
+ );
67
87
  await userEvent.type(screen.getByRole("spinbutton", { name: /q/i }), "9");
68
- await userEvent.type(screen.getByRole("textbox", { name: /available/i }), "true");
88
+ await userEvent.click(screen.getByRole("checkbox", { name: /available/i }));
69
89
  await userEvent.type(screen.getByRole("textbox", { name: /currency/i }), "mxn");
70
- await userEvent.type(
71
- screen.getByRole("textbox", { name: /trade date/i }),
72
- "Thu Jul 21 2022 22:44:10",
73
- );
90
+ pickDatetime(screen.getByRole("textbox", { name: /trade date/i }), tradeDate, TradeDateFormat);
74
91
 
75
92
  await userEvent.click(screen.getByRole("button", { name: /save/i }));
76
93
 
77
- expect(onSubmit).toHaveBeenCalledTimes(1);
78
- expect(onSubmit).toHaveBeenCalledWith({
94
+ expectToHaveBeenCalledOnceWithMockInstance(onSubmit, {
79
95
  id: "Id-1",
80
96
  firstName: "Karianne",
81
97
  middleName: "Noah",
82
98
  lastName: "Gorczany",
83
99
  gender: "Cis Man",
84
- age: "37",
85
- birthDate: "Tue Nov 26 2047 12:14:19",
100
+ age: 37,
101
+ birthDate,
86
102
  car: {
87
103
  model: "Spyder",
88
104
  manufacturer: "Bugatti",
89
105
  color: "red",
90
- type: "Convertible",
106
+ type: ["Coupe", "Hatchback", "Minivan"],
91
107
  vin: "46N6UE4VJ2XL28828",
92
108
  vrm: "NE51AFH",
109
+ returnTime,
93
110
  },
94
- quantity: "9",
95
- available: "true",
111
+ quantity: 9,
112
+ available: true,
96
113
  currency: "mxn",
97
- tradeDate: "Thu Jul 21 2022 22:44:10",
114
+ tradeDate,
98
115
  });
99
116
  });
100
117
  });
@@ -1,8 +1,69 @@
1
- import { Box, Button, Grid, Paper, TextField, Typography } from "@mui/material";
2
- import React, { ChangeEvent, FormEvent } from "react";
1
+ import {
2
+ Box,
3
+ Button,
4
+ Checkbox,
5
+ FormControl,
6
+ FormControlLabel,
7
+ Grid,
8
+ InputLabel,
9
+ ListItemText,
10
+ MenuItem,
11
+ Paper,
12
+ Select,
13
+ SelectChangeEvent,
14
+ TextField,
15
+ Typography,
16
+ } from "@mui/material";
17
+ import { DesktopDatePicker, TimePicker, DateTimePicker } from "@mui/x-date-pickers";
18
+ import React, { ChangeEvent, FormEvent, ReactElement, useMemo } from "react";
3
19
  import { useState } from "react";
4
20
  import { useGetDefaultThemeColor } from "../../utils/theme";
5
- import { Model, ModelField, BasicModelInstance } from "../generators.model";
21
+ import { Model, ModelField, BasicModelInstance, ModelFieldTypes } from "../generators.model";
22
+
23
+ const InitialStateZeroValue: Record<string, any> = {
24
+ string: undefined,
25
+ number: undefined,
26
+ boolean: false,
27
+ enum: "",
28
+ multienum: [],
29
+ date: "01/01/1970",
30
+ group: {},
31
+ };
32
+
33
+ const getFieldInitialState = <T extends BasicModelInstance>(
34
+ field: ModelField,
35
+ initialValues: T | undefined,
36
+ ) => {
37
+ return initialValues ? initialValues[field.id] : InitialStateZeroValue[field.type];
38
+ };
39
+
40
+ const getValuesInitialState = <T extends BasicModelInstance>(
41
+ model: Model,
42
+ initialValues: T | undefined,
43
+ ): T => {
44
+ const obj = {} as any;
45
+
46
+ model.fields.forEach((field) => {
47
+ let value: any;
48
+ if (field.type === "group") {
49
+ value = {};
50
+ field.value.forEach((groupField) => {
51
+ value[groupField.id] = getFieldInitialState(
52
+ groupField,
53
+ initialValues && initialValues[field.id],
54
+ );
55
+ });
56
+ } else if (field.type === "date" || field.type === "time") {
57
+ value = (initialValues && initialValues[field.id]) || field.default;
58
+ } else {
59
+ value = getFieldInitialState(field, initialValues);
60
+ }
61
+
62
+ obj[field.id] = value;
63
+ });
64
+
65
+ return obj;
66
+ };
6
67
 
7
68
  export interface ModelFormProps<T extends BasicModelInstance> {
8
69
  model: Model;
@@ -17,28 +78,63 @@ export const ModelForm = <T extends BasicModelInstance>({
17
78
  onSubmit,
18
79
  initialValues,
19
80
  }: ModelFormProps<T>) => {
20
- const [values, setValues] = useState<T>(initialValues || ({} as T));
21
-
22
- const handleInputChange = (e: ChangeEvent<any>, key: string | undefined) => {
23
- e.preventDefault();
24
-
25
- e.target;
81
+ const valuesInitialState = useMemo(
82
+ () => getValuesInitialState<T>(model, initialValues),
83
+ [model, initialValues],
84
+ );
85
+ const [values, setValues] = useState<T>(valuesInitialState);
26
86
 
87
+ const setKeyValue = (name: string, key: string | undefined, value: any) => {
27
88
  setValues((v) => {
28
89
  const n: Record<string, object> = {};
29
90
  if (key) {
30
91
  n[key] = {
31
92
  ...v[key],
32
- [e.target.name]: e.target.value,
93
+ [name]: value,
33
94
  };
34
95
  } else {
35
- n[e.target.name] = e.target.value;
96
+ n[name] = value;
36
97
  }
37
98
 
38
99
  return { ...v, ...n };
39
100
  });
40
101
  };
41
102
 
103
+ const handleCheckboxChange = (e: ChangeEvent<any>, key: string | undefined) => {
104
+ e.preventDefault();
105
+ setKeyValue(e.target.name, key, e.target.checked);
106
+ };
107
+
108
+ const handleSelectChange = (e: SelectChangeEvent<any>, key: string | undefined) => {
109
+ e.preventDefault();
110
+ setKeyValue(e.target.name, key, e.target.value);
111
+ };
112
+
113
+ const handleMultiSelectChange = (e: SelectChangeEvent<any>, key: string | undefined) => {
114
+ e.preventDefault();
115
+ const { value } = e.target;
116
+ const newValue = typeof value === "string" ? value.split(",") : value;
117
+ setKeyValue(e.target.name, key, newValue);
118
+ };
119
+
120
+ const handleInputChange = (
121
+ e: ChangeEvent<any>,
122
+ key: string | undefined,
123
+ type: ModelFieldTypes,
124
+ ) => {
125
+ e.preventDefault();
126
+
127
+ let value = e.target.value;
128
+ if (type === "number") {
129
+ value = parseInt(e.target.value);
130
+ }
131
+ setKeyValue(e.target.name, key, value);
132
+ };
133
+
134
+ const handleDateChange = (value: any, key: string | undefined, id: string) => {
135
+ setKeyValue(id, key, value);
136
+ };
137
+
42
138
  const handleSubmit = (e: FormEvent<HTMLFormElement>) => {
43
139
  e.preventDefault();
44
140
  onSubmit(values);
@@ -48,28 +144,113 @@ export const ModelForm = <T extends BasicModelInstance>({
48
144
  const defaultColor = useGetDefaultThemeColor({ lightWeight: 200, darkWeight: 800 });
49
145
 
50
146
  const { id, type, name, description, xs, sm, md, lg, xl } = field;
147
+
148
+ let fieldInput: ReactElement;
149
+ const value = key ? values[key][id] : values[id];
51
150
  if (type === "group") {
52
- return (
53
- <Grid item key={id} xs={xs} sm={sm} md={md} lg={lg} xl={xl}>
54
- <Paper>
55
- <Box bgcolor={defaultColor} px={2} py={1} mb={2}>
56
- <Typography variant="h6" role="heading" aria-level={1}>
57
- {name}
58
- </Typography>
59
- <Typography variant="body2" role="heading" aria-level={2}>
60
- {description}
61
- </Typography>
62
- </Box>
63
- <Grid container spacing={2} sx={{ p: 2 }}>
64
- {field.value.map((f) => renderField(f, id))}
65
- </Grid>
66
- </Paper>
67
- </Grid>
151
+ fieldInput = (
152
+ <Paper>
153
+ <Box bgcolor={defaultColor} px={2} py={1} mb={2}>
154
+ <Typography variant="h6" role="heading" aria-level={1}>
155
+ {name}
156
+ </Typography>
157
+ <Typography variant="body2" role="heading" aria-level={2}>
158
+ {description}
159
+ </Typography>
160
+ </Box>
161
+ <Grid container spacing={2} sx={{ p: 2 }}>
162
+ {field.value.map((f) => renderField(f, id))}
163
+ </Grid>
164
+ </Paper>
68
165
  );
69
- }
70
-
71
- return (
72
- <Grid item key={id} xs={xs} sm={sm} md={md} lg={lg} xl={xl}>
166
+ } else if (type === "boolean") {
167
+ fieldInput = (
168
+ <Box sx={{ height: 1, display: "flex", alignItems: "center" }}>
169
+ <FormControlLabel
170
+ control={
171
+ <Checkbox name={id} onChange={(e) => handleCheckboxChange(e, key)} checked={value} />
172
+ }
173
+ label={name}
174
+ />
175
+ </Box>
176
+ );
177
+ } else if (type === "enum") {
178
+ fieldInput = (
179
+ <FormControl fullWidth>
180
+ <InputLabel id={`${id}-select-label`}>{name}</InputLabel>
181
+ <Select
182
+ labelId={`${id}-select-label`}
183
+ id={`${id}-select`}
184
+ value={value}
185
+ label={name}
186
+ name={id}
187
+ onChange={(e) => handleSelectChange(e, key)}
188
+ required
189
+ >
190
+ {field.value.map((fieldValue) => (
191
+ <MenuItem key={fieldValue} value={fieldValue}>
192
+ {fieldValue}
193
+ </MenuItem>
194
+ ))}
195
+ </Select>
196
+ </FormControl>
197
+ );
198
+ } else if (type === "multienum") {
199
+ fieldInput = (
200
+ <FormControl fullWidth>
201
+ <InputLabel id={`${id}-select-label`}>{name}</InputLabel>
202
+ <Select
203
+ labelId={`${id}-select-label`}
204
+ id={`${id}-select`}
205
+ value={value || []}
206
+ renderValue={(selected) => selected.join(", ")}
207
+ label={name}
208
+ name={id}
209
+ onChange={(e) => handleMultiSelectChange(e, key)}
210
+ required
211
+ multiple
212
+ >
213
+ {field.value.map((fieldValue) => (
214
+ <MenuItem key={fieldValue} value={fieldValue}>
215
+ <Checkbox checked={(value || []).includes(fieldValue)} />
216
+ <ListItemText primary={fieldValue} />
217
+ </MenuItem>
218
+ ))}
219
+ </Select>
220
+ </FormControl>
221
+ );
222
+ } else if (type === "date") {
223
+ fieldInput = (
224
+ <DesktopDatePicker
225
+ label={name}
226
+ inputFormat={field.format}
227
+ value={value}
228
+ onChange={(value) => handleDateChange(value, key, id)}
229
+ renderInput={(params: any) => <TextField {...params} />}
230
+ />
231
+ );
232
+ } else if (type === "time") {
233
+ fieldInput = (
234
+ <TimePicker
235
+ label={name}
236
+ inputFormat={field.format}
237
+ value={value}
238
+ onChange={(value) => handleDateChange(value, key, id)}
239
+ renderInput={(params: any) => <TextField {...params} />}
240
+ />
241
+ );
242
+ } else if (type === "datetime") {
243
+ fieldInput = (
244
+ <DateTimePicker
245
+ label={name}
246
+ inputFormat={field.format}
247
+ value={value}
248
+ onChange={(value) => handleDateChange(value, key, id)}
249
+ renderInput={(params: any) => <TextField {...params} />}
250
+ />
251
+ );
252
+ } else {
253
+ fieldInput = (
73
254
  <TextField
74
255
  required
75
256
  type={type}
@@ -77,9 +258,15 @@ export const ModelForm = <T extends BasicModelInstance>({
77
258
  name={id}
78
259
  variant="outlined"
79
260
  fullWidth
80
- value={key && key in values ? values[key][id] : values[id]}
81
- onChange={(e) => handleInputChange(e, key)}
261
+ value={value}
262
+ onChange={(e) => handleInputChange(e, key, type)}
82
263
  />
264
+ );
265
+ }
266
+
267
+ return (
268
+ <Grid item key={id} xs={xs} sm={sm} md={md} lg={lg} xl={xl}>
269
+ {fieldInput}
83
270
  </Grid>
84
271
  );
85
272
  };
@@ -1 +1,2 @@
1
1
  export * from "./model-router";
2
+ export * from "./model-router.types";