@pautena/react-design-system 0.7.2 → 0.7.3

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 (51) 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 +13 -16
  25. package/src/components/feedback/query-container/query-container.stories.tsx +19 -6
  26. package/src/components/feedback/query-container/query-container.test.tsx +41 -17
  27. package/src/components/feedback/query-container/query-container.tsx +17 -5
  28. package/src/components/inputs/index.ts +1 -0
  29. package/src/components/value-displays/group-value-card/group-value-card.mock.tsx +28 -8
  30. package/src/components/value-displays/group-value-card/group-value-card.stories.tsx +19 -2
  31. package/src/components/value-displays/group-value-card/group-value-card.test.tsx +16 -18
  32. package/src/components/value-displays/value-base/value-edit.test.tsx +88 -0
  33. package/src/components/value-displays/value-base/value-edit.tsx +28 -6
  34. package/src/components/value-displays/value-boolean/value-boolean.stories.tsx +9 -0
  35. package/src/components/value-displays/value-boolean/value-boolean.test.tsx +29 -15
  36. package/src/components/value-displays/value-boolean/value-boolean.tsx +18 -11
  37. package/src/components/value-displays/value-content/value-content.test.tsx +20 -6
  38. package/src/components/value-displays/value-content/value-content.tsx +24 -10
  39. package/src/components/value-displays/value-datetime/value-datetime.stories.tsx +11 -0
  40. package/src/components/value-displays/value-datetime/value-datetime.test.tsx +9 -9
  41. package/src/components/value-displays/value-datetime/value-datetime.tsx +14 -10
  42. package/src/components/value-displays/value-rating/value-rating.stories.tsx +10 -0
  43. package/src/components/value-displays/value-rating/value-rating.test.tsx +11 -11
  44. package/src/components/value-displays/value-rating/value-rating.tsx +10 -8
  45. package/src/components/value-displays/value-text/value-text.stories.tsx +9 -0
  46. package/src/components/value-displays/value-text/value-text.test.tsx +20 -9
  47. package/src/components/value-displays/value-text/value-text.tsx +23 -10
  48. package/src/generators/model-form/model-form.test.tsx +1 -1
  49. package/src/generators/model-router/model-router.test.tsx +3 -3
  50. package/src/layouts/header-layout/header-layout.stories.tsx +2 -2
  51. package/src/layouts/header-layout/header-layout.tsx +1 -7
@@ -6,47 +6,71 @@ 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;
15
+ loading?: 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", () => {
28
+ renderComponent({ fetching: true });
26
29
 
27
- expectProgressIndicator();
30
+ expectProgressIndicator();
31
+ });
32
+
33
+ it("should render the children", () => {
34
+ renderComponent({ fetching: true });
35
+
36
+ expect(screen.getByText(/children content/i)).toBeVisible();
37
+ });
38
+ });
39
+
40
+ describe("loading", () => {
41
+ it("should render a loading indicator", () => {
42
+ renderComponent({ loading: true });
43
+
44
+ expectProgressIndicator();
45
+ });
46
+
47
+ it("shouldn't render the children", () => {
48
+ renderComponent({ loading: true });
49
+
50
+ expect(screen.queryByText(/children content/i)).not.toBeInTheDocument();
51
+ });
28
52
  });
29
53
 
30
54
  describe("error", () => {
31
- it("would render an error alert", () => {
55
+ it("should render an error alert", () => {
32
56
  renderComponent({ error: { message: "Internal error" } });
33
57
 
34
58
  expect(screen.getByRole("alert")).toHaveAttribute("aria-describedby", "error");
35
59
  });
36
60
 
37
- it("would render the error message if error is defined", () => {
61
+ it("should render the error message if error is defined", () => {
38
62
  renderComponent({ error: { message: "Internal error" } });
39
63
 
40
64
  expect(screen.getByText(/internal error/i)).toBeInTheDocument();
41
65
  });
42
66
 
43
- it("would render the error title if error.name is defined", () => {
67
+ it("should render the error title if error.name is defined", () => {
44
68
  renderComponent({ error: { name: "Internal error", message: "There was an error" } });
45
69
 
46
70
  expect(screen.getByRole("heading", { name: /internal error/i })).toBeInTheDocument();
47
71
  });
48
72
 
49
- it("wouldn't render the error title if error is defined but the error.name not", () => {
73
+ it("shouldn't render the error title if error is defined but the error.name not", () => {
50
74
  renderComponent({ error: { message: "Internal error" } });
51
75
 
52
76
  expect(screen.queryByRole("heading")).not.toBeInTheDocument();
@@ -54,19 +78,19 @@ describe("QueryContainer", () => {
54
78
  });
55
79
 
56
80
  describe("success", () => {
57
- it("would render a success alert", () => {
81
+ it("should render a success alert", () => {
58
82
  renderComponent({ success: { message: "Item added" } });
59
83
 
60
84
  expect(screen.getByRole("alert")).toHaveAttribute("aria-describedby", "success");
61
85
  });
62
86
 
63
- it("would render the success message if success is defined", () => {
87
+ it("should render the success message if success is defined", () => {
64
88
  renderComponent({ success: { message: "Item added" } });
65
89
 
66
90
  expect(screen.getByText(/item added/i)).toBeInTheDocument();
67
91
  });
68
92
 
69
- it("would render the success title if success.name is defined", () => {
93
+ it("should render the success title if success.name is defined", () => {
70
94
  renderComponent({
71
95
  success: { name: "Item added", message: "The item has been added successfully" },
72
96
  });
@@ -74,22 +98,22 @@ describe("QueryContainer", () => {
74
98
  expect(screen.getByRole("heading", { name: /item added/i })).toBeInTheDocument();
75
99
  });
76
100
 
77
- it("wouldn't render the error title if success is defined but the success.name not", () => {
101
+ it("shouldn't render the error title if success is defined but the success.name not", () => {
78
102
  renderComponent({ success: { message: "Item added" } });
79
103
 
80
104
  expect(screen.queryByRole("heading")).not.toBeInTheDocument();
81
105
  });
82
106
  //TODO validate the severity of the alert
83
107
 
84
- it("would render the content", () => {
108
+ it("should render the content", () => {
85
109
  renderComponent({ success: { message: "Item added" } });
86
110
 
87
111
  expect(screen.getByText(/children content/i)).toBeInTheDocument();
88
112
  });
89
113
  });
90
114
 
91
- it("would render the children if isFetching is false and there is no defined error", () => {
92
- renderComponent({ isFetching: false, error: undefined });
115
+ it("should render the children if isFetching is false and there is no defined error", () => {
116
+ renderComponent({ fetching: false, error: undefined });
93
117
 
94
118
  expect(screen.getByText(/children content/i)).toBeInTheDocument();
95
119
  });
@@ -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;
33
+
34
+ /**
35
+ * There is a query in progress and we don't have available data
36
+ */
37
+ loading: boolean;
33
38
  /**
34
39
  * The query has returned an error
35
40
  */
@@ -43,8 +48,14 @@ 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,
53
+ loading,
54
+ error,
55
+ success,
56
+ children,
57
+ }: QueryContainerProps) {
58
+ if (loading) {
48
59
  return <LoadingArea />;
49
60
  }
50
61
 
@@ -65,6 +76,7 @@ export function QueryContainer({ isFetching, error, success, children }: QueryCo
65
76
  {success.message}
66
77
  </Alert>
67
78
  )}
79
+ {fetching && <LinearProgress sx={{ width: 1, mb: 1 }} />}
68
80
  {children}
69
81
  </Box>
70
82
  );
@@ -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";
@@ -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",
@@ -30,50 +30,64 @@ describe("ValueBoolean", () => {
30
30
  it("would render the label", () => {
31
31
  renderComponent({ value: true });
32
32
 
33
- expect(screen.getByRole("label", { name: /hello world/i })).toBeInTheDocument();
33
+ expect(screen.getByRole("label", { name: /hello world/i })).toBeVisible();
34
34
  });
35
35
 
36
36
  it("would render a check if value is true", () => {
37
37
  renderComponent({ value: true });
38
38
 
39
- expect(screen.getByTestId("CheckIcon")).toBeInTheDocument();
39
+ expect(screen.getByTestId("CheckIcon")).toBeVisible();
40
40
  });
41
41
 
42
42
  it("would render a cross if value is false", () => {
43
43
  renderComponent({ value: false });
44
44
 
45
- expect(screen.getByTestId("CloseIcon")).toBeInTheDocument();
45
+ expect(screen.getByTestId("CloseIcon")).toBeVisible();
46
+ });
47
+
48
+ describe("accessibility", () => {
49
+ it("should be checked if value is true", () => {
50
+ renderComponent({ value: true });
51
+
52
+ expect(screen.getByLabelText(/hello world/i)).toBeChecked();
53
+ });
54
+
55
+ it("shouldn't be checked if value is false", () => {
56
+ renderComponent({ value: false });
57
+
58
+ expect(screen.getByLabelText(/hello world/i)).not.toBeChecked();
59
+ });
46
60
  });
47
61
 
48
62
  it("should render the placeholder if value is undefined", () => {
49
63
  renderComponent({ value: undefined });
50
64
 
51
- expect(screen.getByText(/-/i)).toBeInTheDocument();
65
+ expect(screen.getByText(/-/i)).toBeVisible();
52
66
  });
53
67
 
54
68
  it("should render the custom placeholder if value is undefined and placeholder has value", () => {
55
69
  renderComponent({ value: undefined, placeholder: "_" });
56
70
 
57
- expect(screen.getByText(/_/i)).toBeInTheDocument();
71
+ expect(screen.getByText(/_/i)).toBeVisible();
58
72
  });
59
73
 
60
74
  describe("editable", () => {
61
75
  it("shouldn't render an option to edit if editable is false", () => {
62
76
  renderComponent({ value: true, editable: false });
63
77
 
64
- expect(screen.queryByTestId("EditIcon")).not.toBeInTheDocument();
78
+ expect(screen.queryByRole("button", { name: /edit/i })).not.toBeInTheDocument();
65
79
  });
66
80
 
67
81
  it("should render an option to edit if editable is true", () => {
68
82
  renderComponent({ value: true, editable: true });
69
83
 
70
- expect(screen.getByTestId("EditIcon")).toBeVisible();
84
+ expect(screen.getByRole("button", { name: /edit/i })).toBeVisible();
71
85
  });
72
86
 
73
87
  it("should render an input with the value if the edit button is clicked", async () => {
74
88
  renderComponent({ value: true, editable: true });
75
89
 
76
- await userEvent.click(screen.getByTestId("EditIcon"));
90
+ await userEvent.click(screen.getByRole("button", { name: /edit/i }));
77
91
 
78
92
  expect(screen.getByRole("checkbox")).toBeChecked();
79
93
  });
@@ -86,9 +100,9 @@ describe("ValueBoolean", () => {
86
100
  async (expectedValue: boolean, initialValue: boolean) => {
87
101
  const { onEdit } = renderComponent({ value: initialValue, editable: true });
88
102
 
89
- await userEvent.click(screen.getByTestId("EditIcon"));
103
+ await userEvent.click(screen.getByRole("button", { name: /edit/i }));
90
104
  await userEvent.click(screen.getByRole("checkbox"));
91
- await userEvent.click(screen.getByTestId("CheckIcon"));
105
+ await userEvent.click(screen.getByRole("button", { name: /submit/i }));
92
106
 
93
107
  expect(onEdit).toHaveBeenCalledTimes(1);
94
108
  expect(onEdit).toHaveBeenCalledWith(expectedValue);
@@ -98,9 +112,9 @@ describe("ValueBoolean", () => {
98
112
  it("should not call onEdit if the edition is cancelled", async () => {
99
113
  const { onEdit } = renderComponent({ value: true, editable: true });
100
114
 
101
- await userEvent.click(screen.getByTestId("EditIcon"));
115
+ await userEvent.click(screen.getByRole("button", { name: /edit/i }));
102
116
  await userEvent.click(screen.getByRole("checkbox"));
103
- await userEvent.click(screen.getByTestId("ClearIcon"));
117
+ await userEvent.click(screen.getByRole("button", { name: /cancel/i }));
104
118
 
105
119
  expect(onEdit).not.toHaveBeenCalled();
106
120
  });
@@ -108,10 +122,10 @@ describe("ValueBoolean", () => {
108
122
  it("should have the original value if is edited again after clear a change", async () => {
109
123
  renderComponent({ value: true, editable: true });
110
124
 
111
- await userEvent.click(screen.getByTestId("EditIcon"));
125
+ await userEvent.click(screen.getByRole("button", { name: /edit/i }));
112
126
  await userEvent.click(screen.getByRole("checkbox"));
113
- await userEvent.click(screen.getByTestId("ClearIcon"));
114
- await userEvent.click(screen.getByTestId("EditIcon"));
127
+ await userEvent.click(screen.getByRole("button", { name: /cancel/i }));
128
+ await userEvent.click(screen.getByRole("button", { name: /edit/i }));
115
129
 
116
130
  expect(screen.getByRole("checkbox")).toBeChecked();
117
131
  });