@pautena/react-design-system 0.7.2 → 0.7.4

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 (54) 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/alerts/expandable-alert/expandable-alert.d.ts +4 -1
  4. package/dist/cjs/types/components/data-display/board/board.d.ts +1 -2
  5. package/dist/cjs/types/components/feedback/query-container/query-container.d.ts +7 -3
  6. package/dist/cjs/types/components/inputs/index.d.ts +1 -0
  7. package/dist/cjs/types/components/value-displays/group-value-card/group-value-card.mock.d.ts +3 -1
  8. package/dist/cjs/types/components/value-displays/value-base/value-edit.d.ts +7 -2
  9. package/dist/cjs/types/components/value-displays/value-content/value-content.d.ts +10 -1
  10. package/dist/esm/index.js +4 -4
  11. package/dist/esm/index.js.map +1 -1
  12. package/dist/esm/types/components/alerts/expandable-alert/expandable-alert.d.ts +4 -1
  13. package/dist/esm/types/components/data-display/board/board.d.ts +1 -2
  14. package/dist/esm/types/components/feedback/query-container/query-container.d.ts +7 -3
  15. package/dist/esm/types/components/inputs/index.d.ts +1 -0
  16. package/dist/esm/types/components/value-displays/group-value-card/group-value-card.mock.d.ts +3 -1
  17. package/dist/esm/types/components/value-displays/value-base/value-edit.d.ts +7 -2
  18. package/dist/esm/types/components/value-displays/value-content/value-content.d.ts +10 -1
  19. package/dist/index.d.ts +26 -9
  20. package/package.json +2 -1
  21. package/src/components/alerts/expandable-alert/expandable-alert.stories.tsx +23 -1
  22. package/src/components/alerts/expandable-alert/expandable-alert.tsx +11 -4
  23. package/src/components/data-display/board/board.test.tsx +60 -43
  24. package/src/components/data-display/board/board.tsx +24 -17
  25. package/src/components/feedback/query-container/query-container.stories.tsx +19 -6
  26. package/src/components/feedback/query-container/query-container.test.tsx +65 -17
  27. package/src/components/feedback/query-container/query-container.tsx +20 -5
  28. package/src/components/inputs/autocomplete/autocomplete.stories.tsx +8 -0
  29. package/src/components/inputs/index.ts +1 -0
  30. package/src/components/inputs/text-field/text-field.stories.tsx +8 -0
  31. package/src/components/inputs/text-field/text-field.tsx +2 -2
  32. package/src/components/value-displays/group-value-card/group-value-card.mock.tsx +28 -8
  33. package/src/components/value-displays/group-value-card/group-value-card.stories.tsx +19 -2
  34. package/src/components/value-displays/group-value-card/group-value-card.test.tsx +16 -18
  35. package/src/components/value-displays/value-base/value-edit.test.tsx +88 -0
  36. package/src/components/value-displays/value-base/value-edit.tsx +28 -6
  37. package/src/components/value-displays/value-boolean/value-boolean.stories.tsx +9 -0
  38. package/src/components/value-displays/value-boolean/value-boolean.test.tsx +29 -15
  39. package/src/components/value-displays/value-boolean/value-boolean.tsx +18 -11
  40. package/src/components/value-displays/value-content/value-content.test.tsx +20 -6
  41. package/src/components/value-displays/value-content/value-content.tsx +24 -10
  42. package/src/components/value-displays/value-datetime/value-datetime.stories.tsx +11 -0
  43. package/src/components/value-displays/value-datetime/value-datetime.test.tsx +9 -9
  44. package/src/components/value-displays/value-datetime/value-datetime.tsx +14 -10
  45. package/src/components/value-displays/value-rating/value-rating.stories.tsx +10 -0
  46. package/src/components/value-displays/value-rating/value-rating.test.tsx +11 -11
  47. package/src/components/value-displays/value-rating/value-rating.tsx +10 -8
  48. package/src/components/value-displays/value-text/value-text.stories.tsx +9 -0
  49. package/src/components/value-displays/value-text/value-text.test.tsx +20 -9
  50. package/src/components/value-displays/value-text/value-text.tsx +23 -10
  51. package/src/generators/model-form/model-form.test.tsx +1 -1
  52. package/src/generators/model-router/model-router.test.tsx +3 -3
  53. package/src/layouts/header-layout/header-layout.stories.tsx +2 -2
  54. package/src/layouts/header-layout/header-layout.tsx +1 -7
@@ -6,47 +6,95 @@ import { expectProgressIndicator } from "~/tests/assertions";
6
6
 
7
7
  describe("QueryContainer", () => {
8
8
  const renderComponent = ({
9
- isFetching = false,
9
+ fetching = false,
10
+ loading = false,
10
11
  error = undefined,
11
12
  success = undefined,
12
13
  }: {
13
- isFetching?: boolean;
14
+ fetching?: boolean | boolean[];
15
+ loading?: boolean | boolean[];
14
16
  error?: QueryContainerError;
15
17
  success?: QueryContainerSuccess;
16
18
  } = {}) => {
17
19
  return render(
18
- <QueryContainer isFetching={isFetching} error={error} success={success}>
20
+ <QueryContainer fetching={fetching} loading={loading} error={error} success={success}>
19
21
  <Typography>children content</Typography>
20
22
  </QueryContainer>,
21
23
  );
22
24
  };
23
25
 
24
- it("would render a loading indicator if isFetching is true", () => {
25
- renderComponent({ isFetching: true });
26
+ describe("fetching", () => {
27
+ it("should render a loading indicator if fetching=true", () => {
28
+ renderComponent({ fetching: true });
26
29
 
27
- expectProgressIndicator();
30
+ expectProgressIndicator();
31
+ });
32
+
33
+ it("should render a loading indicator if fetching is an array with a true value", () => {
34
+ renderComponent({ fetching: [false, false, true, false, false] });
35
+
36
+ expectProgressIndicator();
37
+ });
38
+
39
+ it("should render the children if fetching=true", () => {
40
+ renderComponent({ fetching: true });
41
+
42
+ expect(screen.getByText(/children content/i)).toBeVisible();
43
+ });
44
+
45
+ it("should render the children if fetching is an array with a true value", () => {
46
+ renderComponent({ fetching: [false, false, true, false, false] });
47
+
48
+ expect(screen.getByText(/children content/i)).toBeVisible();
49
+ });
50
+ });
51
+
52
+ describe("loading", () => {
53
+ it("should render a loading indicator if loading=true", () => {
54
+ renderComponent({ loading: true });
55
+
56
+ expectProgressIndicator();
57
+ });
58
+
59
+ it("should render a loading indicator if loading is an array with a true value", () => {
60
+ renderComponent({ loading: [false, false, true, false, false] });
61
+
62
+ expectProgressIndicator();
63
+ });
64
+
65
+ it("shouldn't render the children", () => {
66
+ renderComponent({ loading: true });
67
+
68
+ expect(screen.queryByText(/children content/i)).not.toBeInTheDocument();
69
+ });
70
+
71
+ it("shouldn't render the children if fetching is an array with a true value", () => {
72
+ renderComponent({ loading: [false, false, true, false, false] });
73
+
74
+ expect(screen.queryByText(/children content/i)).not.toBeInTheDocument();
75
+ });
28
76
  });
29
77
 
30
78
  describe("error", () => {
31
- it("would render an error alert", () => {
79
+ it("should render an error alert", () => {
32
80
  renderComponent({ error: { message: "Internal error" } });
33
81
 
34
82
  expect(screen.getByRole("alert")).toHaveAttribute("aria-describedby", "error");
35
83
  });
36
84
 
37
- it("would render the error message if error is defined", () => {
85
+ it("should render the error message if error is defined", () => {
38
86
  renderComponent({ error: { message: "Internal error" } });
39
87
 
40
88
  expect(screen.getByText(/internal error/i)).toBeInTheDocument();
41
89
  });
42
90
 
43
- it("would render the error title if error.name is defined", () => {
91
+ it("should render the error title if error.name is defined", () => {
44
92
  renderComponent({ error: { name: "Internal error", message: "There was an error" } });
45
93
 
46
94
  expect(screen.getByRole("heading", { name: /internal error/i })).toBeInTheDocument();
47
95
  });
48
96
 
49
- it("wouldn't render the error title if error is defined but the error.name not", () => {
97
+ it("shouldn't render the error title if error is defined but the error.name not", () => {
50
98
  renderComponent({ error: { message: "Internal error" } });
51
99
 
52
100
  expect(screen.queryByRole("heading")).not.toBeInTheDocument();
@@ -54,19 +102,19 @@ describe("QueryContainer", () => {
54
102
  });
55
103
 
56
104
  describe("success", () => {
57
- it("would render a success alert", () => {
105
+ it("should render a success alert", () => {
58
106
  renderComponent({ success: { message: "Item added" } });
59
107
 
60
108
  expect(screen.getByRole("alert")).toHaveAttribute("aria-describedby", "success");
61
109
  });
62
110
 
63
- it("would render the success message if success is defined", () => {
111
+ it("should render the success message if success is defined", () => {
64
112
  renderComponent({ success: { message: "Item added" } });
65
113
 
66
114
  expect(screen.getByText(/item added/i)).toBeInTheDocument();
67
115
  });
68
116
 
69
- it("would render the success title if success.name is defined", () => {
117
+ it("should render the success title if success.name is defined", () => {
70
118
  renderComponent({
71
119
  success: { name: "Item added", message: "The item has been added successfully" },
72
120
  });
@@ -74,22 +122,22 @@ describe("QueryContainer", () => {
74
122
  expect(screen.getByRole("heading", { name: /item added/i })).toBeInTheDocument();
75
123
  });
76
124
 
77
- it("wouldn't render the error title if success is defined but the success.name not", () => {
125
+ it("shouldn't render the error title if success is defined but the success.name not", () => {
78
126
  renderComponent({ success: { message: "Item added" } });
79
127
 
80
128
  expect(screen.queryByRole("heading")).not.toBeInTheDocument();
81
129
  });
82
130
  //TODO validate the severity of the alert
83
131
 
84
- it("would render the content", () => {
132
+ it("should render the content", () => {
85
133
  renderComponent({ success: { message: "Item added" } });
86
134
 
87
135
  expect(screen.getByText(/children content/i)).toBeInTheDocument();
88
136
  });
89
137
  });
90
138
 
91
- it("would render the children if isFetching is false and there is no defined error", () => {
92
- renderComponent({ isFetching: false, error: undefined });
139
+ it("should render the children if isFetching is false and there is no defined error", () => {
140
+ renderComponent({ fetching: false, error: undefined });
93
141
 
94
142
  expect(screen.getByText(/children content/i)).toBeInTheDocument();
95
143
  });
@@ -1,4 +1,4 @@
1
- import { Alert, AlertTitle, Box } from "@mui/material";
1
+ import { Alert, AlertTitle, Box, LinearProgress } from "@mui/material";
2
2
  import React from "react";
3
3
  import { PropsWithChildren } from "react";
4
4
  import { LoadingArea } from "../loading-area";
@@ -27,9 +27,14 @@ export interface QueryContainerSuccess {
27
27
 
28
28
  export type QueryContainerProps = PropsWithChildren<{
29
29
  /**
30
- * There is a query in progress
30
+ * There is a query in progress and we have available data
31
31
  */
32
- isFetching: boolean;
32
+ fetching?: boolean | boolean[];
33
+
34
+ /**
35
+ * There is a query in progress and we don't have available data
36
+ */
37
+ loading?: boolean | boolean[];
33
38
  /**
34
39
  * The query has returned an error
35
40
  */
@@ -43,8 +48,17 @@ export type QueryContainerProps = PropsWithChildren<{
43
48
  /**
44
49
  * Component to show different indicators based on the usual api query statuses
45
50
  */
46
- export function QueryContainer({ isFetching, error, success, children }: QueryContainerProps) {
47
- if (isFetching) {
51
+ export function QueryContainer({
52
+ fetching: fetchingProp = false,
53
+ loading: loadingProp = false,
54
+ error,
55
+ success,
56
+ children,
57
+ }: QueryContainerProps) {
58
+ const fetching = Array.isArray(fetchingProp) ? fetchingProp.some((f) => f) : fetchingProp;
59
+ const loading = Array.isArray(loadingProp) ? loadingProp.some((f) => f) : loadingProp;
60
+
61
+ if (loading) {
48
62
  return <LoadingArea />;
49
63
  }
50
64
 
@@ -65,6 +79,7 @@ export function QueryContainer({ isFetching, error, success, children }: QueryCo
65
79
  {success.message}
66
80
  </Alert>
67
81
  )}
82
+ {fetching && <LinearProgress sx={{ width: 1, mb: 1 }} />}
68
83
  {children}
69
84
  </Box>
70
85
  );
@@ -53,6 +53,14 @@ export const Fetching: Story = {
53
53
  },
54
54
  };
55
55
 
56
+ export const LoadingAndFetching: Story = {
57
+ args: {
58
+ ...Default.args,
59
+ loading: true,
60
+ fetching: true,
61
+ },
62
+ };
63
+
56
64
  export const SizeSmall: Story = {
57
65
  args: {
58
66
  ...Default.args,
@@ -1,3 +1,4 @@
1
1
  export * from "./select";
2
2
  export * from "./sign-in";
3
3
  export * from "./autocomplete";
4
+ export * from "./text-field";
@@ -53,6 +53,14 @@ export const Fetching: Story = {
53
53
  },
54
54
  };
55
55
 
56
+ export const LoadingAndFetching: Story = {
57
+ args: {
58
+ ...Default.args,
59
+ fetching: true,
60
+ loading: true,
61
+ },
62
+ };
63
+
56
64
  export const FetchingWithEndAdornment: Story = {
57
65
  args: {
58
66
  ...Default.args,
@@ -83,13 +83,13 @@ export const TextField = ({
83
83
  endAdornment={
84
84
  <InputAdornment position="end">
85
85
  {InputProps?.endAdornment}
86
- {fetching ? <CircularProgress color="inherit" size={20} sx={{ ml: 1 }} /> : null}
86
+ {loading ? <CircularProgress color="inherit" size={20} sx={{ ml: 1 }} /> : null}
87
87
  </InputAdornment>
88
88
  }
89
89
  sx={sx}
90
90
  {...(rest as any)}
91
91
  />
92
- {loading && (
92
+ {fetching && !loading && (
93
93
  <LinearProgress
94
94
  color="inherit"
95
95
  sx={{ position: "absolute", left: 0, right: 0, bottom: 0 }}
@@ -4,37 +4,57 @@ import { ValueRating } from "../value-rating";
4
4
  import { ValueText } from "../value-text";
5
5
  import { GroupValueCard, GroupValueCardProps } from "./group-value-card";
6
6
  import { ValueItem } from "../value-item";
7
+ import { ValueDatetime } from "../value-datetime";
7
8
 
8
- export const GroupValueCardDummy = ({ dense, ...rest }: GroupValueCardProps) => {
9
+ export const GroupValueCardDummy = ({
10
+ dense,
11
+ editable,
12
+ ...rest
13
+ }: GroupValueCardProps & { editable?: boolean }) => {
9
14
  return (
10
15
  <GroupValueCard {...rest} dense={dense}>
11
16
  <ValueItem xs={12} sm={6} md={4}>
12
- <ValueText dense={dense} label="Hello world" value="Lorem ipsum sit amet" />
17
+ <ValueText
18
+ editable={editable}
19
+ dense={dense}
20
+ label="Hello world"
21
+ value="Lorem ipsum sit amet"
22
+ />
13
23
  </ValueItem>
14
24
  <ValueItem xs={12} sm={6} md={2}>
15
- <ValueBoolean dense={dense} label="Enabled" value />
25
+ <ValueBoolean editable={editable} dense={dense} label="Enabled" value />
16
26
  </ValueItem>
17
27
  <ValueItem xs={12} sm={6} md={3}>
18
- <ValueText dense={dense} label="Quantity" value="1200" />
28
+ <ValueText editable={editable} dense={dense} label="Quantity" value="1200" />
19
29
  </ValueItem>
20
30
  <ValueItem xs={12} sm={6} md={3}>
21
- <ValueText dense={dense} label="Currency" value="EUR" />
31
+ <ValueText editable={editable} dense={dense} label="Currency" value="EUR" />
22
32
  </ValueItem>
23
33
  <ValueItem xs={12} sm={6} md={6}>
24
34
  <ValueText
35
+ editable={editable}
25
36
  dense={dense}
26
37
  label="I am Batman"
27
38
  value=" Does it come in black? It's ends here. Hero can be anyone"
28
39
  />
29
40
  </ValueItem>
30
41
  <ValueItem xs={12} sm={6} md={3}>
31
- <ValueText dense={dense} label="Status" value="Open" />
42
+ <ValueText editable={editable} dense={dense} label="Status" value="Open" />
32
43
  </ValueItem>
33
44
  <ValueItem xs={12} sm={6} md={3}>
34
- <ValueText dense={dense} label="Level" value="2144" />
45
+ <ValueText editable={editable} dense={dense} label="Level" value="2144" />
35
46
  </ValueItem>
36
47
  <ValueItem xs={12} sm={6} md={3}>
37
- <ValueRating dense={dense} label="Rating" value={3} />
48
+ <ValueRating editable={editable} dense={dense} label="Rating" value={3} />
49
+ </ValueItem>
50
+ <ValueItem xs={12} sm={6} md={3}>
51
+ <ValueDatetime
52
+ editable={editable}
53
+ dense={dense}
54
+ label="Created at"
55
+ value={new Date(2022, 5, 1)}
56
+ format="yyyy-MM-dd"
57
+ />
38
58
  </ValueItem>
39
59
  </GroupValueCard>
40
60
  );
@@ -1,6 +1,6 @@
1
1
  import { Meta, StoryObj } from "@storybook/react";
2
2
  import { GroupValueCard } from "./group-value-card";
3
- import { withPadding } from "../../../storybook";
3
+ import { withPadding, withLocalizationProvider } from "../../../storybook";
4
4
  import { GroupValueCardDummy } from "./group-value-card.mock";
5
5
  import { ValueImage } from "../value-image";
6
6
  import workInProgressImg from "../../../stories/assets/work-in-progress.jpg";
@@ -12,7 +12,7 @@ import { ValueItem } from "../value-item";
12
12
  export default {
13
13
  title: "Components/Value displays/GroupValueCard",
14
14
  component: GroupValueCardDummy,
15
- decorators: [withPadding(2)],
15
+ decorators: [withPadding(2), withLocalizationProvider],
16
16
  parameters: {
17
17
  layout: "fullscreen",
18
18
  },
@@ -34,6 +34,23 @@ export const Dense: Story = {
34
34
  },
35
35
  };
36
36
 
37
+ export const EditableDense: Story = {
38
+ args: {
39
+ title: "Hello world",
40
+ subtitle: "Lorem ipsum sit amet",
41
+ dense: true,
42
+ editable: true,
43
+ },
44
+ };
45
+
46
+ export const Editable: Story = {
47
+ args: {
48
+ title: "Hello world",
49
+ subtitle: "Lorem ipsum sit amet",
50
+ editable: true,
51
+ },
52
+ };
53
+
37
54
  export const WihtoutSubtitle: Story = {
38
55
  args: {
39
56
  title: "Hello world",
@@ -17,7 +17,7 @@ describe("GroupValueCard", () => {
17
17
  it("would render a title", () => {
18
18
  renderComponent({ title: "Hello world" });
19
19
 
20
- expect(screen.getByRole("heading", { level: 1 })).toBeInTheDocument();
20
+ expect(screen.getByRole("heading", { level: 1 })).toBeVisible();
21
21
  });
22
22
 
23
23
  describe("subtitle", () => {
@@ -30,34 +30,32 @@ describe("GroupValueCard", () => {
30
30
  it("would render a subtitle if it's provided", () => {
31
31
  renderComponent({ subtitle: "Hi. I am batman" });
32
32
 
33
- expect(
34
- screen.getByRole("heading", { level: 2, name: /hi. i am batman/i }),
35
- ).toBeInTheDocument();
33
+ expect(screen.getByRole("heading", { level: 2, name: /hi. i am batman/i })).toBeVisible();
36
34
  });
37
35
  });
38
36
 
39
37
  it("would render all values", () => {
40
38
  renderComponent();
41
39
 
42
- expect(screen.getByRole("label", { name: /hello world/i })).toBeInTheDocument();
43
- expect(screen.getByText(/lorem ipsum sit amet/i)).toBeInTheDocument();
40
+ expect(screen.getByRole("label", { name: /hello world/i })).toBeVisible();
41
+ expect(screen.getByText(/lorem ipsum sit amet/i)).toBeVisible();
44
42
 
45
- expect(screen.getByRole("label", { name: /enabled/i })).toBeInTheDocument();
46
- expect(screen.getByTestId("CheckIcon")).toBeInTheDocument();
43
+ expect(screen.getByRole("label", { name: /enabled/i })).toBeVisible();
44
+ expect(screen.getByLabelText(/enabled/i)).toBeChecked();
47
45
 
48
- expect(screen.getByRole("label", { name: /quantity/i })).toBeInTheDocument();
49
- expect(screen.getByText(/1200/i)).toBeInTheDocument();
46
+ expect(screen.getByRole("label", { name: /quantity/i })).toBeVisible();
47
+ expect(screen.getByText(/1200/i)).toBeVisible();
50
48
 
51
- expect(screen.getByRole("label", { name: /currency/i })).toBeInTheDocument();
52
- expect(screen.getByText(/eur/i)).toBeInTheDocument();
49
+ expect(screen.getByRole("label", { name: /currency/i })).toBeVisible();
50
+ expect(screen.getByText(/eur/i)).toBeVisible();
53
51
 
54
- expect(screen.getByRole("label", { name: /i am batman/i })).toBeInTheDocument();
55
- expect(screen.getByText(/does it come in black/i)).toBeInTheDocument();
52
+ expect(screen.getByRole("label", { name: /i am batman/i })).toBeVisible();
53
+ expect(screen.getByText(/does it come in black/i)).toBeVisible();
56
54
 
57
- expect(screen.getByRole("label", { name: /status/i })).toBeInTheDocument();
58
- expect(screen.getByText(/open/i)).toBeInTheDocument();
55
+ expect(screen.getByRole("label", { name: /status/i })).toBeVisible();
56
+ expect(screen.getByText(/open/i)).toBeVisible();
59
57
 
60
- expect(screen.getByRole("label", { name: /level/i })).toBeInTheDocument();
61
- expect(screen.getByText(/2144/i)).toBeInTheDocument();
58
+ expect(screen.getByRole("label", { name: /level/i })).toBeVisible();
59
+ expect(screen.getByText(/2144/i)).toBeVisible();
62
60
  });
63
61
  });
@@ -0,0 +1,88 @@
1
+ import { render, screen } from "~/tests/testing-library";
2
+ import { ValueEditButton, ValueEditButtons } from "./value-edit";
3
+ import React from "react";
4
+ import userEvent from "@testing-library/user-event";
5
+ import { vi } from "vitest";
6
+
7
+ describe("ValueEditButton", () => {
8
+ const renderComponent = () => {
9
+ const onClick = vi.fn();
10
+ render(<ValueEditButton onClick={onClick} />);
11
+
12
+ return { onClick };
13
+ };
14
+
15
+ it("should render a edit icon", () => {
16
+ renderComponent();
17
+
18
+ expect(screen.getByTestId("EditIcon")).toBeVisible();
19
+ });
20
+
21
+ it("should be accessible", () => {
22
+ renderComponent();
23
+
24
+ expect(screen.getByRole("button", { name: /edit button/i })).toBeVisible();
25
+ });
26
+
27
+ it("should call onClick when the button is clicked", async () => {
28
+ const { onClick } = renderComponent();
29
+
30
+ await userEvent.click(screen.getByRole("button", { name: /edit button/i }));
31
+
32
+ expect(onClick).toHaveBeenCalledTimes(1);
33
+ });
34
+ });
35
+
36
+ describe("ValueEditButtons", () => {
37
+ const renderComponent = () => {
38
+ const onClickCancel = vi.fn();
39
+ const onClickSubmit = vi.fn();
40
+ render(<ValueEditButtons onClickCancel={onClickCancel} onClickSubmit={onClickSubmit} />);
41
+
42
+ return { onClickCancel, onClickSubmit };
43
+ };
44
+
45
+ describe("cancel button", () => {
46
+ it("should render a cancel icon", () => {
47
+ renderComponent();
48
+
49
+ expect(screen.getByTestId("ClearIcon")).toBeVisible();
50
+ });
51
+
52
+ it("should be accessible", () => {
53
+ renderComponent();
54
+
55
+ expect(screen.getByRole("button", { name: /cancel button/i })).toBeVisible();
56
+ });
57
+
58
+ it("should call onClickCancel when the button is clicked", async () => {
59
+ const { onClickCancel } = renderComponent();
60
+
61
+ await userEvent.click(screen.getByRole("button", { name: /cancel button/i }));
62
+
63
+ expect(onClickCancel).toHaveBeenCalledTimes(1);
64
+ });
65
+ });
66
+
67
+ describe("submit button", () => {
68
+ it("should render a submit icon", () => {
69
+ renderComponent();
70
+
71
+ expect(screen.getByTestId("CheckIcon")).toBeVisible();
72
+ });
73
+
74
+ it("should be accessible", () => {
75
+ renderComponent();
76
+
77
+ expect(screen.getByRole("button", { name: /submit button/i })).toBeVisible();
78
+ });
79
+
80
+ it("should call onClickCancel when the button is clicked", async () => {
81
+ const { onClickSubmit } = renderComponent();
82
+
83
+ await userEvent.click(screen.getByRole("button", { name: /submit button/i }));
84
+
85
+ expect(onClickSubmit).toHaveBeenCalledTimes(1);
86
+ });
87
+ });
88
+ });
@@ -1,22 +1,24 @@
1
1
  import React, { useState } from "react";
2
2
  import CheckIcon from "@mui/icons-material/Check";
3
3
  import ClearIcon from "@mui/icons-material/Clear";
4
- import { Button, InputAdornment, SxProps, Theme } from "@mui/material";
4
+ import { Button, IconButton, InputAdornment, SxProps, Theme, useTheme } from "@mui/material";
5
+ import EditIcon from "@mui/icons-material/Edit";
5
6
 
6
7
  export interface ValueEditButtonsProps {
7
8
  onClickCancel: () => void;
8
- onSubmitEdit: () => void;
9
+ onClickSubmit: () => void;
9
10
  sx?: SxProps<Theme>;
10
11
  }
11
12
 
12
- export const ValueEditButtons = ({ onClickCancel, onSubmitEdit, sx }: ValueEditButtonsProps) => {
13
+ export const ValueEditButtons = ({ onClickCancel, onClickSubmit, sx }: ValueEditButtonsProps) => {
13
14
  return (
14
15
  <InputAdornment position="end" sx={sx}>
15
16
  <Button
16
17
  variant="contained"
17
18
  size="small"
18
19
  color="error"
19
- startIcon={<ClearIcon />}
20
+ aria-label="cancel button"
21
+ startIcon={<ClearIcon sx={{ fontSize: 12 }} />}
20
22
  onClick={onClickCancel}
21
23
  sx={{ paddingRight: 0, minWidth: 0, marginRight: 1 }}
22
24
  />
@@ -24,8 +26,9 @@ export const ValueEditButtons = ({ onClickCancel, onSubmitEdit, sx }: ValueEditB
24
26
  variant="contained"
25
27
  size="small"
26
28
  color="primary"
27
- startIcon={<CheckIcon />}
28
- onClick={onSubmitEdit}
29
+ aria-label="submit button"
30
+ startIcon={<CheckIcon sx={{ fontSize: 12 }} />}
31
+ onClick={onClickSubmit}
29
32
  sx={{ paddingRight: 0, minWidth: 0 }}
30
33
  />
31
34
  </InputAdornment>
@@ -57,3 +60,22 @@ export const useEditableValueDisplay = <T,>(
57
60
 
58
61
  return { isEditing, cancelEdit, editValue, setEditValue, startEdit, submitEdit };
59
62
  };
63
+
64
+ export interface ValueEditButtonProps {
65
+ dense?: boolean;
66
+ onClick: () => void;
67
+ }
68
+
69
+ export const ValueEditButton = ({ dense, onClick }: ValueEditButtonProps) => {
70
+ const { typography } = useTheme();
71
+ return (
72
+ <IconButton
73
+ size="small"
74
+ onClick={onClick}
75
+ sx={{ ml: dense ? 0.5 : 1 }}
76
+ aria-label="edit button"
77
+ >
78
+ <EditIcon sx={{ fontSize: typography.pxToRem(dense ? 18 : 24) }} />
79
+ </IconButton>
80
+ );
81
+ };
@@ -31,6 +31,15 @@ export const Editable: Story = {
31
31
  },
32
32
  };
33
33
 
34
+ export const DenseEditable: Story = {
35
+ args: {
36
+ label: "Lorem ipsum",
37
+ value: false,
38
+ dense: true,
39
+ editable: true,
40
+ },
41
+ };
42
+
34
43
  export const Dense: Story = {
35
44
  args: {
36
45
  label: "Lorem ipsum",