@pautena/react-design-system 0.4.6 → 0.4.8

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 (49) hide show
  1. package/dist/cjs/index.js +4 -4
  2. package/dist/cjs/index.js.map +1 -1
  3. package/dist/cjs/types/components/value-displays/index.d.ts +3 -1
  4. package/dist/cjs/types/components/value-displays/value-base/index.d.ts +2 -0
  5. package/dist/cjs/types/components/value-displays/{value-displays.types.d.ts → value-base/value-displays.types.d.ts} +10 -0
  6. package/dist/cjs/types/components/value-displays/value-base/value-edit.d.ts +19 -0
  7. package/dist/cjs/types/components/value-displays/value-boolean/value-boolean.d.ts +3 -3
  8. package/dist/cjs/types/components/value-displays/value-datetime/value-datetime.d.ts +8 -3
  9. package/dist/cjs/types/components/value-displays/value-image/value-image.d.ts +1 -1
  10. package/dist/cjs/types/components/value-displays/value-rating/index.d.ts +1 -0
  11. package/dist/cjs/types/components/value-displays/value-rating/value-rating.d.ts +8 -0
  12. package/dist/cjs/types/components/value-displays/value-text/value-text.d.ts +3 -3
  13. package/dist/esm/index.js +4 -4
  14. package/dist/esm/index.js.map +1 -1
  15. package/dist/esm/types/components/value-displays/index.d.ts +3 -1
  16. package/dist/esm/types/components/value-displays/value-base/index.d.ts +2 -0
  17. package/dist/esm/types/components/value-displays/{value-displays.types.d.ts → value-base/value-displays.types.d.ts} +10 -0
  18. package/dist/esm/types/components/value-displays/value-base/value-edit.d.ts +19 -0
  19. package/dist/esm/types/components/value-displays/value-boolean/value-boolean.d.ts +3 -3
  20. package/dist/esm/types/components/value-displays/value-datetime/value-datetime.d.ts +8 -3
  21. package/dist/esm/types/components/value-displays/value-image/value-image.d.ts +1 -1
  22. package/dist/esm/types/components/value-displays/value-rating/index.d.ts +1 -0
  23. package/dist/esm/types/components/value-displays/value-rating/value-rating.d.ts +8 -0
  24. package/dist/esm/types/components/value-displays/value-text/value-text.d.ts +3 -3
  25. package/dist/index.d.ts +49 -8
  26. package/package.json +1 -1
  27. package/src/components/data-display/header/header.tsx +3 -3
  28. package/src/components/drawers/drawer-collapsable-item/drawer-collapsable-item.tsx +1 -3
  29. package/src/components/value-displays/group-value-card/group-value-card.mock.tsx +4 -0
  30. package/src/components/value-displays/index.ts +3 -1
  31. package/src/components/value-displays/value-base/index.ts +2 -0
  32. package/src/components/value-displays/{value-displays.types.ts → value-base/value-displays.types.ts} +12 -0
  33. package/src/components/value-displays/value-base/value-edit.tsx +59 -0
  34. package/src/components/value-displays/value-boolean/value-boolean.stories.tsx +7 -0
  35. package/src/components/value-displays/value-boolean/value-boolean.test.tsx +81 -2
  36. package/src/components/value-displays/value-boolean/value-boolean.tsx +33 -8
  37. package/src/components/value-displays/value-datetime/value-datetime.stories.tsx +29 -2
  38. package/src/components/value-displays/value-datetime/value-datetime.test.tsx +94 -5
  39. package/src/components/value-displays/value-datetime/value-datetime.tsx +64 -6
  40. package/src/components/value-displays/value-displays.stories.mdx +1 -0
  41. package/src/components/value-displays/value-image/value-image.tsx +1 -1
  42. package/src/components/value-displays/value-rating/index.ts +1 -0
  43. package/src/components/value-displays/value-rating/value-rating.stories.tsx +35 -0
  44. package/src/components/value-displays/value-rating/value-rating.test.tsx +104 -0
  45. package/src/components/value-displays/value-rating/value-rating.tsx +45 -0
  46. package/src/components/value-displays/value-text/value-text.stories.tsx +13 -0
  47. package/src/components/value-displays/value-text/value-text.test.tsx +84 -2
  48. package/src/components/value-displays/value-text/value-text.tsx +34 -7
  49. package/src/tests/actions.ts +2 -2
@@ -1,19 +1,42 @@
1
1
  import React from "react";
2
2
  import { render, screen } from "~/tests/testing-library";
3
- import { ValueDatetime } from "./value-datetime";
3
+ import { EditInputType, ValueDatetime } from "./value-datetime";
4
+ import userEvent from "@testing-library/user-event";
5
+ import { pickDatetime } from "~/tests/actions";
4
6
 
5
- const DummyValue = new Date(2022, 7, 10);
7
+ const DummyValue = new Date(2022, 7, 10, 8, 11);
8
+ const NewValue = new Date(2021, 8, 9, 11, 21);
9
+ const datetimeFormat = "dd-MM-yyyy HH:mm";
10
+ const dateFormat = "dd-MM-yyyy";
11
+ const timeFormat = "HH:mm";
6
12
 
7
13
  describe("ValueDatetime", () => {
8
- const renderComponent = ({ value, placeholder }: { value?: Date; placeholder?: string }) => {
9
- return render(
14
+ const renderComponent = ({
15
+ value,
16
+ placeholder,
17
+ editable,
18
+ fmt = datetimeFormat,
19
+ editInputType = "datetime",
20
+ }: {
21
+ value?: Date;
22
+ placeholder?: string;
23
+ editable?: boolean;
24
+ fmt?: string;
25
+ editInputType?: EditInputType;
26
+ }) => {
27
+ const onEdit = jest.fn();
28
+ render(
10
29
  <ValueDatetime
11
30
  label="Hello world"
12
31
  value={value}
13
32
  placeholder={placeholder}
14
- format="dd-MM-yyyy"
33
+ format={fmt}
34
+ editable={editable}
35
+ editInputType={editInputType}
36
+ onEdit={onEdit}
15
37
  />,
16
38
  );
39
+ return { onEdit };
17
40
  };
18
41
 
19
42
  it("would render the label", () => {
@@ -39,4 +62,70 @@ describe("ValueDatetime", () => {
39
62
 
40
63
  expect(screen.getByText(/_/i)).toBeInTheDocument();
41
64
  });
65
+
66
+ describe("editable", () => {
67
+ it("shouldn't render an option to edit if editable is false", () => {
68
+ renderComponent({ value: DummyValue, editable: false });
69
+
70
+ expect(screen.queryByTestId("EditIcon")).not.toBeInTheDocument();
71
+ });
72
+
73
+ it("should render an option to edit if editable is true", () => {
74
+ renderComponent({ value: DummyValue, editable: true });
75
+
76
+ expect(screen.getByTestId("EditIcon")).toBeVisible();
77
+ });
78
+
79
+ it("should render an input with the value if the edit button is clicked", async () => {
80
+ renderComponent({ value: DummyValue, editable: true });
81
+
82
+ await userEvent.click(screen.getByTestId("EditIcon"));
83
+
84
+ expect(screen.getByRole("textbox")).toHaveValue("10-08-2022 08:11");
85
+ });
86
+
87
+ it.each([
88
+ ["datetime" as EditInputType, new Date(2021, 8, 9, 11, 21), datetimeFormat],
89
+ ["date" as EditInputType, new Date(2021, 8, 9), dateFormat],
90
+ ["time" as EditInputType, new Date(2022, 7, 10, 11, 21), timeFormat],
91
+ ])(
92
+ "should submit the new value if is edited with type %s",
93
+ async (editInputType: EditInputType, expectedDate: Date, fmt: string) => {
94
+ const { onEdit } = renderComponent({
95
+ value: DummyValue,
96
+ editable: true,
97
+ editInputType,
98
+ fmt,
99
+ });
100
+
101
+ await userEvent.click(screen.getByTestId("EditIcon"));
102
+ pickDatetime(screen.getByRole("textbox"), NewValue, fmt);
103
+ await userEvent.click(screen.getByTestId("CheckIcon"));
104
+
105
+ expect(onEdit).toHaveBeenCalledTimes(1);
106
+ expect(onEdit).toHaveBeenCalledWith(expectedDate);
107
+ },
108
+ );
109
+
110
+ it("should not call onEdit if the edition is cancelled", async () => {
111
+ const { onEdit } = renderComponent({ value: DummyValue, editable: true });
112
+
113
+ await userEvent.click(screen.getByTestId("EditIcon"));
114
+ pickDatetime(screen.getByRole("textbox"), NewValue, datetimeFormat);
115
+ await userEvent.click(screen.getByTestId("ClearIcon"));
116
+
117
+ expect(onEdit).not.toHaveBeenCalled();
118
+ });
119
+
120
+ it("should have the original value if is edited again after clear a change", async () => {
121
+ renderComponent({ value: DummyValue, editable: true });
122
+
123
+ await userEvent.click(screen.getByTestId("EditIcon"));
124
+ pickDatetime(screen.getByRole("textbox"), NewValue, datetimeFormat);
125
+ await userEvent.click(screen.getByTestId("ClearIcon"));
126
+ await userEvent.click(screen.getByTestId("EditIcon"));
127
+
128
+ expect(screen.getByRole("textbox")).toHaveValue("10-08-2022 08:11");
129
+ });
130
+ });
42
131
  });
@@ -1,14 +1,28 @@
1
- import { Typography } from "@mui/material";
1
+ import { Box, IconButton, InputAdornment, TextField, Typography } from "@mui/material";
2
2
  import React from "react";
3
3
  import { format } from "date-fns";
4
- import { BaseValueProps, DefaultPlaceholder } from "../value-displays.types";
4
+ import {
5
+ BaseValueProps,
6
+ DefaultPlaceholder,
7
+ EditableValueProps,
8
+ } from "../value-base/value-displays.types";
5
9
  import { getValueContentLabelId, ValueContent } from "../value-content";
10
+ import { useEditableValueDisplay, ValueEditButtons } from "../value-base";
11
+ import EditIcon from "@mui/icons-material/Edit";
12
+ import { DatePicker, DateTimePicker, TimePicker } from "@mui/x-date-pickers";
6
13
 
7
- export interface ValueDatetimeProps extends BaseValueProps<Date> {
14
+ export type EditInputType = "datetime" | "date" | "time";
15
+
16
+ export interface ValueDatetimeProps extends BaseValueProps<Date>, EditableValueProps<Date> {
8
17
  /**
9
18
  * Datetime format
10
19
  */
11
20
  format: string;
21
+
22
+ /**
23
+ * Edit input type
24
+ */
25
+ editInputType?: EditInputType;
12
26
  }
13
27
 
14
28
  /**
@@ -19,15 +33,59 @@ export const ValueDatetime = ({
19
33
  value: valueProp,
20
34
  format: fmt,
21
35
  placeholder = DefaultPlaceholder,
36
+ editable,
37
+ editInputType = "datetime",
38
+ onEdit = () => null,
22
39
  }: ValueDatetimeProps) => {
40
+ const { isEditing, editValue, startEdit, cancelEdit, setEditValue, submitEdit } =
41
+ useEditableValueDisplay(valueProp, onEdit);
23
42
  const id = getValueContentLabelId(label);
24
43
  const value = (valueProp && format(valueProp, fmt)) || placeholder;
25
44
 
45
+ const EditPickerComponent =
46
+ editInputType === "datetime"
47
+ ? DateTimePicker
48
+ : editInputType === "time"
49
+ ? TimePicker
50
+ : DatePicker;
51
+
26
52
  return (
27
53
  <ValueContent label={label} tooltip={value}>
28
- <Typography variant="h5" noWrap aria-labelledby={id}>
29
- {value}
30
- </Typography>
54
+ {isEditing ? (
55
+ <EditPickerComponent
56
+ value={editValue}
57
+ inputFormat={fmt}
58
+ onChange={(newValue: any) => setEditValue(newValue)}
59
+ renderInput={(params: any) => (
60
+ <TextField
61
+ {...params}
62
+ InputProps={{
63
+ endAdornment: (
64
+ <>
65
+ {params.InputProps.endAdornment}
66
+ <ValueEditButtons
67
+ onClickCancel={cancelEdit}
68
+ onSubmitEdit={submitEdit}
69
+ sx={{ ml: 2 }}
70
+ />
71
+ </>
72
+ ),
73
+ }}
74
+ />
75
+ )}
76
+ />
77
+ ) : (
78
+ <Box display="flex" alignItems="center">
79
+ <Typography variant="h5" noWrap aria-labelledby={id}>
80
+ {value}
81
+ </Typography>
82
+ {editable && (
83
+ <IconButton size="small" onClick={startEdit} sx={{ ml: 1 }}>
84
+ <EditIcon />
85
+ </IconButton>
86
+ )}
87
+ </Box>
88
+ )}
31
89
  </ValueContent>
32
90
  );
33
91
  };
@@ -13,4 +13,5 @@ import LinkTo from '@storybook/addon-links/react';
13
13
  <li><LinkTo kind="Components/Value displays/ValueDatetime">ValueDatetime</LinkTo></li>
14
14
  <li><LinkTo kind="Components/Value displays/ValueImage">ValueImage</LinkTo></li>
15
15
  <li><LinkTo kind="Components/Value displays/ValueText">ValueText</LinkTo></li>
16
+ <li><LinkTo kind="Components/Value displays/ValueRating">ValueRating</LinkTo></li>
16
17
  </ul>
@@ -1,7 +1,7 @@
1
1
  import { Box } from "@mui/material";
2
2
  import React from "react";
3
3
  import { ValueContent } from "../value-content";
4
- import { BaseValueProps } from "../value-displays.types";
4
+ import { BaseValueProps } from "../value-base/value-displays.types";
5
5
 
6
6
  export interface ValueImageProps extends BaseValueProps<string> {
7
7
  /**
@@ -0,0 +1 @@
1
+ export * from "./value-rating";
@@ -0,0 +1,35 @@
1
+ import { ComponentMeta } from "@storybook/react";
2
+ import { ValueRating } from "./value-rating";
3
+ import { createTemplate, withContainer } from "../../../storybook";
4
+
5
+ export default {
6
+ title: "Components/Value displays/ValueRating",
7
+ component: ValueRating,
8
+ decorators: [withContainer({ width: 200 })],
9
+ parameters: {
10
+ layout: "centered",
11
+ },
12
+ } as ComponentMeta<typeof ValueRating>;
13
+
14
+ const Template = createTemplate(ValueRating);
15
+
16
+ export const Default = Template.bind({});
17
+ Default.args = {
18
+ label: "Lorem",
19
+ value: 3,
20
+ };
21
+
22
+ export const Max = Template.bind({});
23
+ Max.args = {
24
+ label: "Lorem",
25
+ value: 4,
26
+ maxRating: 7,
27
+ };
28
+
29
+ export const Editable = Template.bind({});
30
+ Editable.args = {
31
+ label: "Lorem",
32
+ value: 4,
33
+ maxRating: 7,
34
+ editable: true,
35
+ };
@@ -0,0 +1,104 @@
1
+ import React from "react";
2
+ import { render, screen } from "~/tests/testing-library";
3
+ import { ValueRating } from "./value-rating";
4
+ import userEvent from "@testing-library/user-event";
5
+
6
+ describe("ValueRating", () => {
7
+ const renderComponent = ({
8
+ maxRating,
9
+ value = 3,
10
+ editable,
11
+ }: { maxRating?: number; value?: number; editable?: boolean } = {}) => {
12
+ const onEdit = jest.fn();
13
+ render(
14
+ <ValueRating
15
+ label="hello world"
16
+ value={value}
17
+ maxRating={maxRating}
18
+ editable={editable}
19
+ onEdit={onEdit}
20
+ />,
21
+ );
22
+ return { onEdit };
23
+ };
24
+
25
+ it("should render a label", () => {
26
+ renderComponent();
27
+
28
+ expect(screen.getByRole("label", { name: /hello world/i })).toBeInTheDocument();
29
+ });
30
+
31
+ it("should render 5 stars by default", () => {
32
+ renderComponent();
33
+
34
+ expect(screen.getAllByTestId("StarIcon")).toHaveLength(3);
35
+ expect(screen.getAllByTestId("StarBorderIcon")).toHaveLength(2);
36
+ });
37
+
38
+ it("should render maxRating stars if is set", () => {
39
+ renderComponent({ maxRating: 7 });
40
+
41
+ expect(screen.getAllByTestId("StarIcon")).toHaveLength(3);
42
+ expect(screen.getAllByTestId("StarBorderIcon")).toHaveLength(4);
43
+ });
44
+
45
+ it("should render 3 filled stars", () => {
46
+ renderComponent();
47
+
48
+ expect(screen.getAllByTestId("StarIcon")).toHaveLength(3);
49
+ });
50
+
51
+ describe("editable", () => {
52
+ it("shouldn't render an option to edit if editable is false", () => {
53
+ renderComponent({ editable: false });
54
+
55
+ expect(screen.queryByTestId("EditIcon")).not.toBeInTheDocument();
56
+ });
57
+
58
+ it("should render an option to edit if editable is true", () => {
59
+ renderComponent({ editable: true });
60
+
61
+ expect(screen.getByTestId("EditIcon")).toBeVisible();
62
+ });
63
+
64
+ it("should render an input for each star + 1 for the zero value if the edit button is clicked", async () => {
65
+ renderComponent({ editable: true });
66
+
67
+ await userEvent.click(screen.getByTestId("EditIcon"));
68
+
69
+ expect(screen.getAllByRole("radio")).toHaveLength(6);
70
+ });
71
+
72
+ it("should submit the new value if is edited", async () => {
73
+ const { onEdit } = renderComponent({ value: 2, editable: true });
74
+
75
+ await userEvent.click(screen.getByTestId("EditIcon"));
76
+ await userEvent.click(screen.getAllByRole("radio")[3]); // Click the fourth star
77
+ await userEvent.click(screen.getByTestId("CheckIcon"));
78
+
79
+ expect(onEdit).toHaveBeenCalledTimes(1);
80
+ expect(onEdit).toHaveBeenCalledWith(4);
81
+ });
82
+
83
+ it("should not call onEdit if the edition is cancelled", async () => {
84
+ const { onEdit } = renderComponent({ editable: true });
85
+
86
+ await userEvent.click(screen.getByTestId("EditIcon"));
87
+ await userEvent.click(screen.getAllByRole("radio")[3]);
88
+ await userEvent.click(screen.getByTestId("ClearIcon"));
89
+
90
+ expect(onEdit).not.toHaveBeenCalled();
91
+ });
92
+
93
+ it("should have the original value if is edited again after clear a change", async () => {
94
+ renderComponent({ editable: true });
95
+
96
+ await userEvent.click(screen.getByTestId("EditIcon"));
97
+ await userEvent.click(screen.getAllByRole("radio")[3]);
98
+ await userEvent.click(screen.getByTestId("ClearIcon"));
99
+ await userEvent.click(screen.getByTestId("EditIcon"));
100
+
101
+ expect(screen.getAllByTestId("StarIcon")).toHaveLength(3);
102
+ });
103
+ });
104
+ });
@@ -0,0 +1,45 @@
1
+ import { Box, IconButton, InputAdornment, Rating } from "@mui/material";
2
+ import React from "react";
3
+ import { useEditableValueDisplay, ValueEditButtons } from "../value-base";
4
+ import { BaseValueProps, EditableValueProps } from "../value-base/value-displays.types";
5
+ import { getValueContentLabelId, ValueContent } from "../value-content";
6
+ import EditIcon from "@mui/icons-material/Edit";
7
+
8
+ export type ValueRatingProps = BaseValueProps<number> & {
9
+ maxRating?: number;
10
+ } & EditableValueProps<number>;
11
+
12
+ /**
13
+ * Displays a number using stars
14
+ */
15
+ export const ValueRating = ({
16
+ label,
17
+ value = 0,
18
+ maxRating = 5,
19
+ editable,
20
+ onEdit = () => null,
21
+ }: ValueRatingProps) => {
22
+ const { isEditing, editValue, startEdit, cancelEdit, setEditValue, submitEdit } =
23
+ useEditableValueDisplay(value, onEdit);
24
+ const id = getValueContentLabelId(label);
25
+
26
+ return (
27
+ <ValueContent label={label} tooltip={value.toString()}>
28
+ <Box display="flex" alignItems="center">
29
+ <Rating
30
+ aria-labelledby={id}
31
+ readOnly={!isEditing}
32
+ max={maxRating}
33
+ value={isEditing ? editValue : value}
34
+ onChange={(_, newValue) => setEditValue(newValue!)}
35
+ />
36
+ {editable && !isEditing && (
37
+ <IconButton size="small" onClick={startEdit} sx={{ ml: 1 }}>
38
+ <EditIcon />
39
+ </IconButton>
40
+ )}
41
+ {isEditing && <ValueEditButtons onClickCancel={cancelEdit} onSubmitEdit={submitEdit} />}
42
+ </Box>
43
+ </ValueContent>
44
+ );
45
+ };
@@ -19,6 +19,12 @@ Default.args = {
19
19
  value: "ipsum sit",
20
20
  };
21
21
 
22
+ export const NumberAsValue = Template.bind({});
23
+ NumberAsValue.args = {
24
+ label: "Lorem",
25
+ value: 1000,
26
+ };
27
+
22
28
  export const TruncatedText = Template.bind({});
23
29
  TruncatedText.args = {
24
30
  label: "Lorem",
@@ -35,3 +41,10 @@ CustomPlaceholder.args = {
35
41
  label: "Lorem",
36
42
  placeholder: ".",
37
43
  };
44
+
45
+ export const Editable = Template.bind({});
46
+ Editable.args = {
47
+ label: "Lorem",
48
+ value: "lorem ipsum",
49
+ editable: true,
50
+ };
@@ -1,12 +1,31 @@
1
1
  import React from "react";
2
2
  import { render, screen } from "~/tests/testing-library";
3
3
  import { ValueText } from "./value-text";
4
+ import userEvent from "@testing-library/user-event";
4
5
 
5
6
  const DummyValue = "Lorem ipsum sit amet";
6
7
 
7
8
  describe("ValueText", () => {
8
- const renderComponent = ({ value, placeholder }: { value?: string; placeholder?: string }) => {
9
- return render(<ValueText label="Hello world" value={value} placeholder={placeholder} />);
9
+ const renderComponent = ({
10
+ value,
11
+ placeholder,
12
+ editable,
13
+ }: {
14
+ value?: string | number;
15
+ placeholder?: string;
16
+ editable?: boolean;
17
+ }) => {
18
+ const onEdit = jest.fn();
19
+ render(
20
+ <ValueText
21
+ label="Hello world"
22
+ value={value}
23
+ placeholder={placeholder}
24
+ editable={editable}
25
+ onEdit={onEdit}
26
+ />,
27
+ );
28
+ return { onEdit };
10
29
  };
11
30
 
12
31
  it("would render the label", () => {
@@ -21,6 +40,12 @@ describe("ValueText", () => {
21
40
  expect(screen.getByText(/lorem ipsum sit amet/i)).toBeInTheDocument();
22
41
  });
23
42
 
43
+ it("would render the value if is a number", () => {
44
+ renderComponent({ value: 1000 });
45
+
46
+ expect(screen.getByText(/1000/i)).toBeInTheDocument();
47
+ });
48
+
24
49
  it("should render the placeholder if value is undefined", () => {
25
50
  renderComponent({ value: undefined });
26
51
 
@@ -32,4 +57,61 @@ describe("ValueText", () => {
32
57
 
33
58
  expect(screen.getByText(/_/i)).toBeInTheDocument();
34
59
  });
60
+
61
+ describe("editable", () => {
62
+ it("shouldn't render an option to edit if editable is false", () => {
63
+ renderComponent({ value: DummyValue, editable: false });
64
+
65
+ expect(screen.queryByTestId("EditIcon")).not.toBeInTheDocument();
66
+ });
67
+
68
+ it("should render an option to edit if editable is true", () => {
69
+ renderComponent({ value: DummyValue, editable: true });
70
+
71
+ expect(screen.getByTestId("EditIcon")).toBeVisible();
72
+ });
73
+
74
+ it("should render an input with the value if the edit button is clicked", async () => {
75
+ renderComponent({ value: DummyValue, editable: true });
76
+
77
+ await userEvent.click(screen.getByTestId("EditIcon"));
78
+
79
+ expect(screen.getByRole("textbox")).toHaveValue(DummyValue);
80
+ });
81
+
82
+ it("should submit the new value if is edited", async () => {
83
+ const { onEdit } = renderComponent({ value: DummyValue, editable: true });
84
+
85
+ await userEvent.click(screen.getByTestId("EditIcon"));
86
+ await userEvent.clear(screen.getByRole("textbox"));
87
+ await userEvent.type(screen.getByRole("textbox"), "new value");
88
+ await userEvent.click(screen.getByTestId("CheckIcon"));
89
+
90
+ expect(onEdit).toHaveBeenCalledTimes(1);
91
+ expect(onEdit).toHaveBeenCalledWith("new value");
92
+ });
93
+
94
+ it("should not call onEdit if the edition is cancelled", async () => {
95
+ const { onEdit } = renderComponent({ value: DummyValue, editable: true });
96
+
97
+ await userEvent.click(screen.getByTestId("EditIcon"));
98
+ await userEvent.clear(screen.getByRole("textbox"));
99
+ await userEvent.type(screen.getByRole("textbox"), "new value");
100
+ await userEvent.click(screen.getByTestId("ClearIcon"));
101
+
102
+ expect(onEdit).not.toHaveBeenCalled();
103
+ });
104
+
105
+ it("should have the original value if is edited again after clear a change", async () => {
106
+ renderComponent({ value: DummyValue, editable: true });
107
+
108
+ await userEvent.click(screen.getByTestId("EditIcon"));
109
+ await userEvent.clear(screen.getByRole("textbox"));
110
+ await userEvent.type(screen.getByRole("textbox"), "new value");
111
+ await userEvent.click(screen.getByTestId("ClearIcon"));
112
+ await userEvent.click(screen.getByTestId("EditIcon"));
113
+
114
+ expect(screen.getByRole("textbox")).toHaveValue(DummyValue);
115
+ });
116
+ });
35
117
  });
@@ -1,9 +1,16 @@
1
- import { Typography } from "@mui/material";
1
+ import { IconButton, InputAdornment, TextField, Typography } from "@mui/material";
2
2
  import React from "react";
3
- import { BaseValueProps, DefaultPlaceholder } from "../value-displays.types";
3
+ import {
4
+ BaseValueProps,
5
+ DefaultPlaceholder,
6
+ EditableValueProps,
7
+ useEditableValueDisplay,
8
+ ValueEditButtons,
9
+ } from "../value-base";
4
10
  import { getValueContentLabelId, ValueContent } from "../value-content";
11
+ import EditIcon from "@mui/icons-material/Edit";
5
12
 
6
- export type ValueTextProps = BaseValueProps<string>;
13
+ export type ValueTextProps = BaseValueProps<string | number> & EditableValueProps<string>;
7
14
 
8
15
  /**
9
16
  * Displays a string value with a label
@@ -12,15 +19,35 @@ export const ValueText = ({
12
19
  label,
13
20
  value: valueProp,
14
21
  placeholder = DefaultPlaceholder,
22
+ editable,
23
+ onEdit = () => null,
15
24
  }: ValueTextProps) => {
25
+ const { isEditing, editValue, startEdit, cancelEdit, setEditValue, submitEdit } =
26
+ useEditableValueDisplay(valueProp?.toString(), onEdit);
16
27
  const id = getValueContentLabelId(label);
17
- const value = valueProp || placeholder;
28
+ const value = valueProp?.toString() || placeholder;
18
29
 
19
30
  return (
20
31
  <ValueContent label={label} tooltip={value}>
21
- <Typography variant="h5" noWrap aria-labelledby={id}>
22
- {value}
23
- </Typography>
32
+ {isEditing ? (
33
+ <TextField
34
+ value={editValue}
35
+ size="small"
36
+ onChange={(e) => setEditValue(e.target.value)}
37
+ InputProps={{
38
+ endAdornment: <ValueEditButtons onClickCancel={cancelEdit} onSubmitEdit={submitEdit} />,
39
+ }}
40
+ />
41
+ ) : (
42
+ <Typography variant="h5" noWrap aria-labelledby={id}>
43
+ {value}
44
+ {editable && (
45
+ <IconButton size="small" onClick={startEdit} sx={{ ml: 1 }}>
46
+ <EditIcon />
47
+ </IconButton>
48
+ )}
49
+ </Typography>
50
+ )}
24
51
  </ValueContent>
25
52
  );
26
53
  };
@@ -42,6 +42,6 @@ export const clearCheckbox = async (element: HTMLInputElement) => {
42
42
  }
43
43
  };
44
44
 
45
- export const pickDatetime = (element: HTMLInputElement, value: Date, fmt: string) => {
46
- fireEvent.change(element, { target: { value: format(value, fmt) } });
45
+ export const pickDatetime = (element: HTMLInputElement, valueArg: Date, fmt: string) => {
46
+ fireEvent.change(element, { target: { value: format(valueArg, fmt) } });
47
47
  };