@pautena/react-design-system 0.1.2 → 0.1.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 (175) hide show
  1. package/package.json +1 -1
  2. package/src/components/app-bar/app-bar.stories.tsx +54 -0
  3. package/src/components/app-bar/app-bar.test.tsx +142 -0
  4. package/src/components/app-bar/app-bar.tsx +150 -0
  5. package/src/components/app-bar/app-bar.types.ts +17 -0
  6. package/src/components/app-bar/index.ts +3 -0
  7. package/src/components/app-bar/mini-app-bar/index.ts +1 -0
  8. package/src/components/app-bar/mini-app-bar/mini-app-bar.tsx +31 -0
  9. package/src/components/bullet/bullet.stories.tsx +43 -0
  10. package/src/components/bullet/bullet.test.tsx +24 -0
  11. package/src/components/bullet/bullet.tsx +30 -0
  12. package/src/components/bullet/index.ts +1 -0
  13. package/src/components/center-container/center-container.stories.tsx +50 -0
  14. package/src/components/center-container/center-container.test.tsx +16 -0
  15. package/src/components/center-container/center-container.tsx +32 -0
  16. package/src/components/center-container/index.ts +1 -0
  17. package/src/components/content/content.stories.tsx +23 -0
  18. package/src/components/content/content.test.tsx +26 -0
  19. package/src/components/content/content.tsx +11 -0
  20. package/src/components/content/content.types.ts +5 -0
  21. package/src/components/content/index.ts +2 -0
  22. package/src/components/drawer/__snapshots__/drawer.test.tsx.snap +20 -0
  23. package/src/components/drawer/drawer.context.ts +20 -0
  24. package/src/components/drawer/drawer.mixins.ts +24 -0
  25. package/src/components/drawer/drawer.mock.tsx +100 -0
  26. package/src/components/drawer/drawer.provider.tsx +23 -0
  27. package/src/components/drawer/drawer.test.tsx +97 -0
  28. package/src/components/drawer/drawer.tsx +30 -0
  29. package/src/components/drawer/drawer.types.ts +53 -0
  30. package/src/components/drawer/index.ts +5 -0
  31. package/src/components/drawer/mini-drawer/index.ts +1 -0
  32. package/src/components/drawer/mini-drawer/mini-drawer.stories.tsx +34 -0
  33. package/src/components/drawer/mini-drawer/mini-drawer.tsx +67 -0
  34. package/src/components/drawer-content/drawer-content.stories.tsx +29 -0
  35. package/src/components/drawer-content/drawer-content.test.tsx +34 -0
  36. package/src/components/drawer-content/drawer-content.tsx +18 -0
  37. package/src/components/drawer-content/index.ts +1 -0
  38. package/src/components/drawer-item/drawer-item.stories.tsx +62 -0
  39. package/src/components/drawer-item/drawer-item.test.tsx +119 -0
  40. package/src/components/drawer-item/drawer-item.tsx +71 -0
  41. package/src/components/drawer-item/index.ts +1 -0
  42. package/src/components/drawer-section/drawer-section.mock.tsx +39 -0
  43. package/src/components/drawer-section/drawer-section.stories.tsx +28 -0
  44. package/src/components/drawer-section/drawer-section.test.tsx +44 -0
  45. package/src/components/drawer-section/drawer-section.tsx +40 -0
  46. package/src/components/drawer-section/index.ts +1 -0
  47. package/src/components/header/header.dummy.ts +55 -0
  48. package/src/components/header/header.stories.tsx +116 -0
  49. package/src/components/header/header.test.tsx +169 -0
  50. package/src/components/header/header.tsx +121 -0
  51. package/src/components/header/header.types.ts +61 -0
  52. package/src/components/header/index.ts +2 -0
  53. package/src/components/index.ts +18 -0
  54. package/src/components/label/index.ts +1 -0
  55. package/src/components/label/label.stories.tsx +49 -0
  56. package/src/components/label/label.test.tsx +30 -0
  57. package/src/components/label/label.tsx +60 -0
  58. package/src/components/link/index.ts +1 -0
  59. package/src/components/link/link.tsx +17 -0
  60. package/src/components/loading-area/index.ts +1 -0
  61. package/src/components/loading-area/loading-area.stories.tsx +17 -0
  62. package/src/components/loading-area/loading-area.test.tsx +11 -0
  63. package/src/components/loading-area/loading-area.tsx +13 -0
  64. package/src/components/placeholder/index.ts +1 -0
  65. package/src/components/placeholder/placeholder.mock.ts +15 -0
  66. package/src/components/placeholder/placeholder.stories.tsx +44 -0
  67. package/src/components/placeholder/placeholder.test.tsx +76 -0
  68. package/src/components/placeholder/placeholder.tsx +75 -0
  69. package/src/components/query-container/index.ts +1 -0
  70. package/src/components/query-container/query-container.stories.tsx +68 -0
  71. package/src/components/query-container/query-container.test.tsx +95 -0
  72. package/src/components/query-container/query-container.tsx +71 -0
  73. package/src/components/sign-in/index.ts +1 -0
  74. package/src/components/sign-in/sign-in.stories.tsx +36 -0
  75. package/src/components/sign-in/sign-in.test.tsx +95 -0
  76. package/src/components/sign-in/sign-in.tsx +97 -0
  77. package/src/components/tab/index.ts +2 -0
  78. package/src/components/tab/tab-card/index.ts +1 -0
  79. package/src/components/tab/tab-card/tab-card.dummy.tsx +30 -0
  80. package/src/components/tab/tab-card/tab-card.stories.tsx +22 -0
  81. package/src/components/tab/tab-card/tab-card.test.tsx +53 -0
  82. package/src/components/tab/tab-card/tab-card.tsx +27 -0
  83. package/src/components/tab/tab-panel/index.ts +1 -0
  84. package/src/components/tab/tab-panel/tab-panel.test.tsx +26 -0
  85. package/src/components/tab/tab-panel/tab-panel.tsx +27 -0
  86. package/src/components/table/enhanced-remote-table/enhanced-remote-table.mock.tsx +27 -0
  87. package/src/components/table/enhanced-remote-table/enhanced-remote-table.stories.tsx +24 -0
  88. package/src/components/table/enhanced-remote-table/enhanced-remote-table.test.tsx +77 -0
  89. package/src/components/table/enhanced-remote-table/enhanced-remote-table.tsx +74 -0
  90. package/src/components/table/enhanced-remote-table/index.ts +1 -0
  91. package/src/components/table/enhanced-table/enhanced-table-head.tsx +58 -0
  92. package/src/components/table/enhanced-table/enhanced-table.mock.tsx +93 -0
  93. package/src/components/table/enhanced-table/enhanced-table.stories.tsx +21 -0
  94. package/src/components/table/enhanced-table/enhanced-table.test.tsx +107 -0
  95. package/src/components/table/enhanced-table/enhanced-table.tsx +136 -0
  96. package/src/components/table/enhanced-table/index.ts +2 -0
  97. package/src/components/table/index.ts +2 -0
  98. package/src/components/table-list/index.ts +1 -0
  99. package/src/components/table-list/table-list.stories.tsx +75 -0
  100. package/src/components/table-list/table-list.test.tsx +284 -0
  101. package/src/components/table-list/table-list.tsx +127 -0
  102. package/src/components/value-displays/group-value-card/group-value-card.mock.tsx +35 -0
  103. package/src/components/value-displays/group-value-card/group-value-card.stories.tsx +26 -0
  104. package/src/components/value-displays/group-value-card/group-value-card.test.tsx +58 -0
  105. package/src/components/value-displays/group-value-card/group-value-card.tsx +63 -0
  106. package/src/components/value-displays/group-value-card/index.ts +1 -0
  107. package/src/components/value-displays/index.ts +4 -0
  108. package/src/components/value-displays/value-boolean/index.ts +1 -0
  109. package/src/components/value-displays/value-boolean/value-boolean.stories.tsx +25 -0
  110. package/src/components/value-displays/value-boolean/value-boolean.test.tsx +27 -0
  111. package/src/components/value-displays/value-boolean/value-boolean.tsx +33 -0
  112. package/src/components/value-displays/value-card/index.ts +1 -0
  113. package/src/components/value-displays/value-card/value-card.stories.tsx +22 -0
  114. package/src/components/value-displays/value-card/value-card.test.tsx +18 -0
  115. package/src/components/value-displays/value-card/value-card.tsx +12 -0
  116. package/src/components/value-displays/value-text/index.ts +1 -0
  117. package/src/components/value-displays/value-text/value-test.test.tsx +21 -0
  118. package/src/components/value-displays/value-text/value-text.stories.tsx +26 -0
  119. package/src/components/value-displays/value-text/value-text.tsx +32 -0
  120. package/src/generators/generators.mock.ts +238 -0
  121. package/src/generators/generators.model.ts +46 -0
  122. package/src/generators/index.ts +4 -0
  123. package/src/generators/model-form/index.ts +1 -0
  124. package/src/generators/model-form/model-form.stories.tsx +30 -0
  125. package/src/generators/model-form/model-form.test.tsx +100 -0
  126. package/src/generators/model-form/model-form.tsx +97 -0
  127. package/src/generators/model-router/index.ts +1 -0
  128. package/src/generators/model-router/model-router.test.tsx +666 -0
  129. package/src/generators/model-router/model-router.tsx +29 -0
  130. package/src/generators/model-router/model-router.types.ts +14 -0
  131. package/src/generators/model-router/screens/add-screen.tsx +69 -0
  132. package/src/generators/model-router/screens/details-screen.tsx +62 -0
  133. package/src/generators/model-router/screens/index.ts +4 -0
  134. package/src/generators/model-router/screens/list-screen.tsx +110 -0
  135. package/src/generators/model-router/screens/screens.types.ts +13 -0
  136. package/src/generators/model-router/screens/update-screen.tsx +96 -0
  137. package/src/generators/model-router/stories/details-screen.stories.tsx +38 -0
  138. package/src/generators/model-router/stories/list-screen.stories.tsx +45 -0
  139. package/src/generators/model-router/stories/model-router.stories.tsx +164 -0
  140. package/src/generators/model-router/stories/templates.tsx +39 -0
  141. package/src/generators/object-details/index.ts +1 -0
  142. package/src/generators/object-details/object-details.stories.tsx +20 -0
  143. package/src/generators/object-details/object-details.test.tsx +21 -0
  144. package/src/generators/object-details/object-details.tsx +76 -0
  145. package/src/index.ts +4 -0
  146. package/src/layouts/app-bar-with-drawer-layout/app-bar-with-drawer-layout.stories.tsx +28 -0
  147. package/src/layouts/app-bar-with-drawer-layout/app-bar-with-drawer-layout.test.tsx +30 -0
  148. package/src/layouts/app-bar-with-drawer-layout/app-bar-with-drawer-layout.tsx +37 -0
  149. package/src/layouts/app-bar-with-drawer-layout/index.ts +1 -0
  150. package/src/layouts/header-layout/header-layout.stories.tsx +204 -0
  151. package/src/layouts/header-layout/header-layout.test.tsx +37 -0
  152. package/src/layouts/header-layout/header-layout.tsx +23 -0
  153. package/src/layouts/header-layout/index.ts +1 -0
  154. package/src/layouts/index.ts +2 -0
  155. package/src/providers/index.ts +2 -0
  156. package/src/providers/notification-center/index.ts +2 -0
  157. package/src/providers/notification-center/notification-center.context.ts +37 -0
  158. package/src/providers/notification-center/notification-center.provider.tsx +51 -0
  159. package/src/providers/notification-center/notification-center.stories.tsx +52 -0
  160. package/src/providers/notification-center/notification-center.test.tsx +112 -0
  161. package/src/providers/tab-provider/index.ts +2 -0
  162. package/src/providers/tab-provider/tab-provider.context.ts +8 -0
  163. package/src/providers/tab-provider/tab-provider.provider.tsx +13 -0
  164. package/src/storybook.tsx +90 -0
  165. package/src/tests/assertions.ts +76 -0
  166. package/src/tests/components.tsx +60 -0
  167. package/src/tests/content-placeholder.stories.tsx +16 -0
  168. package/src/tests/index.ts +3 -0
  169. package/src/tests/skeleton-card.stories.tsx +18 -0
  170. package/src/tests/testing-library.tsx +65 -0
  171. package/src/utils/arrays.test.ts +9 -0
  172. package/src/utils/arrays.ts +7 -0
  173. package/src/utils/index.ts +2 -0
  174. package/src/utils/theme.ts +11 -0
  175. package/.prettierrc.js +0 -5
@@ -0,0 +1,121 @@
1
+ import React from "react";
2
+ import {
3
+ Breadcrumbs,
4
+ Typography,
5
+ Container,
6
+ useTheme,
7
+ Box,
8
+ Tabs,
9
+ Tab,
10
+ Button,
11
+ } from "@mui/material";
12
+ import { Link } from "../link";
13
+ import { useGetDefaultThemeColor } from "../../utils";
14
+ import { HeaderComponent, HeaderPreset, HeaderProps } from "./header.types";
15
+ import { useTab } from "~/providers";
16
+
17
+ /**
18
+ * Section used to explain give basic information about the page
19
+ * and put the main actions
20
+ */
21
+ export const Header: HeaderComponent = ({
22
+ title,
23
+ subtitle,
24
+ preset = "default",
25
+ actionsVariant = "outlined",
26
+ breadcrumbs,
27
+ actions,
28
+ tabs,
29
+ }: HeaderProps) => {
30
+ const { palette } = useTheme();
31
+ const defaultColor = useGetDefaultThemeColor();
32
+ const [selectedTab, setSelectedTab] = useTab();
33
+
34
+ const bgColorPresets: Record<HeaderPreset, string> = {
35
+ default: defaultColor,
36
+ primary: palette.primary.main,
37
+ secondary: palette.secondary.main,
38
+ inherit: "inherit",
39
+ transparent: "transparent",
40
+ };
41
+ const bgColor = bgColorPresets[preset];
42
+ const textColorPresets: Record<HeaderPreset, string> = {
43
+ default: palette.getContrastText(bgColorPresets.default),
44
+ primary: palette.primary.contrastText,
45
+ secondary: palette.secondary.contrastText,
46
+ inherit: "inherit",
47
+ transparent: palette.text.primary,
48
+ };
49
+ const textColor = textColorPresets[preset];
50
+
51
+ return (
52
+ <Box bgcolor={bgColor} color={textColor}>
53
+ <Container>
54
+ <Box sx={{ py: 3, display: "flex", flexDirection: "row", justifyContent: "space-between" }}>
55
+ <Box>
56
+ {breadcrumbs?.length && (
57
+ <Breadcrumbs
58
+ color="inherit"
59
+ separator="›"
60
+ aria-label="breadcrumb"
61
+ sx={{ marginTop: 1 }}
62
+ >
63
+ {breadcrumbs.map(({ id, link, text }) => (
64
+ <Link
65
+ key={id}
66
+ underline="hover"
67
+ color="inherit"
68
+ href={link}
69
+ variant="body2"
70
+ role="link"
71
+ >
72
+ {text}
73
+ </Link>
74
+ ))}
75
+ </Breadcrumbs>
76
+ )}
77
+ <Typography variant="h4" role="heading" aria-level={1}>
78
+ {title}
79
+ </Typography>
80
+ {subtitle && (
81
+ <Typography variant="body1" role="heading" aria-level={2}>
82
+ {subtitle}
83
+ </Typography>
84
+ )}
85
+ </Box>
86
+ {actions && (
87
+ <Box>
88
+ {actions.map(({ disabled, id, href, onClick, text }, i) => (
89
+ <Button
90
+ component={href ? Link : "button"}
91
+ role="button"
92
+ color="inherit"
93
+ disabled={disabled}
94
+ key={id}
95
+ variant={actionsVariant}
96
+ size="small"
97
+ href={href}
98
+ onClick={onClick}
99
+ sx={{ mr: i != actions.length - 1 ? 1 : 0 }}
100
+ >
101
+ {text}
102
+ </Button>
103
+ ))}
104
+ </Box>
105
+ )}
106
+ </Box>
107
+ {tabs && (
108
+ <Tabs
109
+ value={selectedTab}
110
+ textColor="inherit"
111
+ onChange={(_, index) => setSelectedTab(index)}
112
+ >
113
+ {tabs.map(({ id, label, disabled }) => (
114
+ <Tab key={id} label={label} disabled={disabled} />
115
+ ))}
116
+ </Tabs>
117
+ )}
118
+ </Container>
119
+ </Box>
120
+ );
121
+ };
@@ -0,0 +1,61 @@
1
+ import { PropTypes } from "@mui/material";
2
+ import { FunctionComponent, ReactElement } from "react";
3
+
4
+ export type HeaderPreset = PropTypes.Color | "transparent";
5
+ export type HeaderActionVariant = "text" | "outlined" | "contained";
6
+
7
+ export type HeaderAction = {
8
+ id: string;
9
+ text: string;
10
+ disabled?: boolean;
11
+ href?: string;
12
+ onClick?: () => void;
13
+ };
14
+
15
+ export interface HeaderBreadcrumb {
16
+ id: string;
17
+ text: string;
18
+ link: string;
19
+ }
20
+
21
+ export interface HeaderTab {
22
+ id: string;
23
+ label: string;
24
+ disabled?: boolean;
25
+ }
26
+
27
+ export type HeaderProps = {
28
+ /**
29
+ * Title of the header
30
+ */
31
+ title: string;
32
+ /**
33
+ * Subtitle of the header
34
+ */
35
+ subtitle?: string;
36
+ /**
37
+ * Color palete used to render the component
38
+ */
39
+ preset?: HeaderPreset;
40
+ /**
41
+ * List of breadcumbs to represent the path to reach
42
+ * the page that we are
43
+ */
44
+ breadcrumbs?: HeaderBreadcrumb[];
45
+ /**
46
+ * List of actions that can be performed by the user.
47
+ * Each action will be a button in the header.
48
+ */
49
+ actions?: HeaderAction[];
50
+ /**
51
+ * Variant used to render the actions
52
+ */
53
+ actionsVariant?: HeaderActionVariant;
54
+ /**
55
+ * If is set, a list of tabs is dispayed at the bottom
56
+ */
57
+ tabs?: HeaderTab[];
58
+ };
59
+
60
+ export type HeaderComponent = FunctionComponent<HeaderProps>;
61
+ export type HeaderElement = ReactElement<HeaderProps, HeaderComponent>;
@@ -0,0 +1,2 @@
1
+ export * from "./header";
2
+ export * from "./header.types";
@@ -0,0 +1,18 @@
1
+ export * from "./header";
2
+ export * from "./link";
3
+ export * from "./query-container";
4
+ export * from "./sign-in";
5
+ export * from "./tab";
6
+ export * from "./table";
7
+ export * from "./app-bar";
8
+ export * from "./drawer";
9
+ export * from "./table-list";
10
+ export * from "./placeholder";
11
+ export * from "./label";
12
+ export * from "./bullet";
13
+ export * from "./drawer-content";
14
+ export * from "./drawer-section";
15
+ export * from "./drawer-item";
16
+ export * from "./center-container";
17
+ export * from "./value-displays";
18
+ export * from "./content";
@@ -0,0 +1 @@
1
+ export * from "./label";
@@ -0,0 +1,49 @@
1
+ import { ComponentMeta } from "@storybook/react";
2
+ import { createTemplate } from "../../storybook";
3
+ import { Label } from "./label";
4
+
5
+ export default {
6
+ title: "Data Display/Label",
7
+ component: Label,
8
+ parameters: {
9
+ layout: "centered",
10
+ },
11
+ } as ComponentMeta<typeof Label>;
12
+
13
+ const Template = createTemplate(Label);
14
+
15
+ export const Default = Template.bind({});
16
+ Default.args = {
17
+ text: "lorem",
18
+ variant: "default",
19
+ };
20
+
21
+ export const Primary = Template.bind({});
22
+ Primary.args = {
23
+ text: "lorem",
24
+ variant: "primary",
25
+ };
26
+
27
+ export const Secondary = Template.bind({});
28
+ Secondary.args = {
29
+ text: "lorem",
30
+ variant: "secondary",
31
+ };
32
+
33
+ export const Info = Template.bind({});
34
+ Info.args = {
35
+ text: "lorem",
36
+ variant: "info",
37
+ };
38
+
39
+ export const Warning = Template.bind({});
40
+ Warning.args = {
41
+ text: "lorem",
42
+ variant: "warning",
43
+ };
44
+
45
+ export const Error = Template.bind({});
46
+ Error.args = {
47
+ text: "lorem",
48
+ variant: "error",
49
+ };
@@ -0,0 +1,30 @@
1
+ import React from "react";
2
+ import { Label, LabelVariant } from "./label";
3
+ import { render, screen } from "../../tests";
4
+
5
+ describe("Label", () => {
6
+ const renderComponent = (variant: LabelVariant | undefined = undefined) => {
7
+ return render(<Label variant={variant} text="lorem ipsum" />);
8
+ };
9
+
10
+ it("renders the label text", () => {
11
+ renderComponent();
12
+
13
+ expect(screen.getByText("LOREM IPSUM")).toBeInTheDocument();
14
+ });
15
+
16
+ it("renders as default without a variant", () => {
17
+ renderComponent(undefined);
18
+
19
+ expect(screen.getByRole("label")).toHaveAttribute("aria-describedby", "default");
20
+ });
21
+
22
+ it.each([["primary"], ["secondary"], ["default"], ["info"], ["warning"], ["error"]])(
23
+ "renders correctly with variant %s",
24
+ (variant: string) => {
25
+ renderComponent(variant as LabelVariant);
26
+
27
+ expect(screen.getByRole("label")).toHaveAttribute("aria-describedby", variant);
28
+ },
29
+ );
30
+ });
@@ -0,0 +1,60 @@
1
+ import { Box, Typography, useTheme } from "@mui/material";
2
+ import React from "react";
3
+
4
+ export type LabelVariant = "primary" | "secondary" | "default" | "info" | "warning" | "error";
5
+
6
+ export const labelClasses = {
7
+ root: "RdsLabel-root",
8
+ };
9
+
10
+ export interface LabelProps {
11
+ /**
12
+ * Content of the component
13
+ */
14
+ text: string;
15
+ /**
16
+ * Color palette used to draw the component
17
+ */
18
+ variant?: LabelVariant;
19
+ }
20
+
21
+ /**
22
+ * Compact element to represent a text
23
+ */
24
+ export const Label = ({ text, variant = "default" }: LabelProps) => {
25
+ const { palette } = useTheme();
26
+
27
+ const backgroundColor: Record<LabelVariant, string> = {
28
+ default: palette.mode === "light" ? palette.grey[100] : palette.grey[900],
29
+ primary: palette.primary.main,
30
+ secondary: palette.secondary.main,
31
+ info: palette.info.main,
32
+ warning: palette.warning.main,
33
+ error: palette.error.main,
34
+ };
35
+
36
+ const textColor: Record<LabelVariant, string> = {
37
+ default: palette.getContrastText(backgroundColor.default),
38
+ primary: palette.primary.contrastText,
39
+ secondary: palette.secondary.contrastText,
40
+ info: palette.info.contrastText,
41
+ warning: palette.warning.contrastText,
42
+ error: palette.error.contrastText,
43
+ };
44
+
45
+ return (
46
+ <Box
47
+ px={1}
48
+ sx={{ backgroundColor: backgroundColor[variant] }}
49
+ borderRadius={1}
50
+ color={textColor[variant]}
51
+ className={labelClasses.root}
52
+ role="label"
53
+ aria-describedby={variant}
54
+ >
55
+ <Typography variant="caption" fontWeight={700}>
56
+ {text.toUpperCase()}
57
+ </Typography>
58
+ </Box>
59
+ );
60
+ };
@@ -0,0 +1 @@
1
+ export * from "./link";
@@ -0,0 +1,17 @@
1
+ import React from "react";
2
+ import { Link as RouterLink, LinkProps as RouterLinkProps } from "react-router-dom";
3
+ import { forwardRef } from "react";
4
+ import { LinkProps, Link as MuiLink } from "@mui/material";
5
+
6
+ // eslint-disable-next-line react/display-name, @typescript-eslint/no-explicit-any
7
+ export const LinkBehaviour = forwardRef<
8
+ any,
9
+ Omit<RouterLinkProps, "to"> & { href: RouterLinkProps["to"] }
10
+ >((props, ref) => {
11
+ const { href, ...other } = props;
12
+ return <RouterLink ref={ref} to={href} {...other} />;
13
+ });
14
+
15
+ export const Link = forwardRef<any, LinkProps>((props, _) => {
16
+ return <MuiLink {...props} component={LinkBehaviour} />;
17
+ });
@@ -0,0 +1 @@
1
+ export * from "./loading-area";
@@ -0,0 +1,17 @@
1
+ import { ComponentMeta } from "@storybook/react";
2
+ import { createTemplate, withContainer } from "../../storybook";
3
+ import { LoadingArea } from "./loading-area";
4
+
5
+ export default {
6
+ title: "Components/LoadingArea",
7
+ component: LoadingArea,
8
+ decorators: [withContainer({ height: 300 })],
9
+ parameters: {
10
+ layout: "fullscreen",
11
+ },
12
+ } as ComponentMeta<typeof LoadingArea>;
13
+
14
+ const Template = createTemplate(LoadingArea);
15
+
16
+ export const Default = Template.bind({});
17
+ Default.args = {};
@@ -0,0 +1,11 @@
1
+ import React from "react";
2
+ import { render, expectProgressIndicator } from "../../tests";
3
+ import { LoadingArea } from "./loading-area";
4
+
5
+ describe("LoadingArea", () => {
6
+ it("would render a loading icon", () => {
7
+ render(<LoadingArea />);
8
+
9
+ expectProgressIndicator();
10
+ });
11
+ });
@@ -0,0 +1,13 @@
1
+ import { CircularProgress, Box } from "@mui/material";
2
+ import React from "react";
3
+
4
+ /**
5
+ * Displays a centered loading indicator
6
+ */
7
+ export const LoadingArea = () => {
8
+ return (
9
+ <Box width={1} height={1} display="flex" justifyContent="center" alignItems="center">
10
+ <CircularProgress />
11
+ </Box>
12
+ );
13
+ };
@@ -0,0 +1 @@
1
+ export * from "./placeholder";
@@ -0,0 +1,15 @@
1
+ import { PlaceholderAction } from "./placeholder";
2
+ import { action } from "@storybook/addon-actions";
3
+
4
+ export const actions: PlaceholderAction[] = [
5
+ {
6
+ id: "add",
7
+ text: "Add",
8
+ href: "/placeholders/add",
9
+ },
10
+ {
11
+ id: "edit",
12
+ text: "Edit",
13
+ onClick: action("on click edit action"),
14
+ },
15
+ ];
@@ -0,0 +1,44 @@
1
+ import React from "react";
2
+ import { ComponentMeta } from "@storybook/react";
3
+ import { createTemplate } from "../../storybook";
4
+ import { Placeholder, PlaceholderIconArgs } from "./placeholder";
5
+ import SearchIcon from "@mui/icons-material/Search";
6
+ import { actions } from "./placeholder.mock";
7
+
8
+ export default {
9
+ title: "Components/Placeholder",
10
+ component: Placeholder,
11
+ parameters: {
12
+ layout: "fullscreen",
13
+ },
14
+ } as ComponentMeta<typeof Placeholder>;
15
+
16
+ const Template = createTemplate(Placeholder);
17
+
18
+ export const Default = Template.bind({});
19
+ Default.args = {
20
+ title: "Lorem ipsum dolor sit amet",
21
+ subtitle:
22
+ "Lorem ipsum dolor sit amet, consectetur adipiscing elit. Morbi eleifend at libero in tristique. Pellentesque bibendum arcu eget augue commodo, non convallis eros porttitor",
23
+ };
24
+
25
+ export const WithIcon = Template.bind({});
26
+ WithIcon.args = {
27
+ icon: ({ size, color }: PlaceholderIconArgs) => (
28
+ <SearchIcon color={color} sx={{ fontSize: size }} />
29
+ ),
30
+ title: "Lorem ipsum dolor sit amet",
31
+ subtitle:
32
+ "Lorem ipsum dolor sit amet, consectetur adipiscing elit. Morbi eleifend at libero in tristique. Pellentesque bibendum arcu eget augue commodo, non convallis eros porttitor",
33
+ };
34
+
35
+ export const WithActions = Template.bind({});
36
+ WithActions.args = {
37
+ icon: ({ size, color }: PlaceholderIconArgs) => (
38
+ <SearchIcon color={color} sx={{ fontSize: size }} />
39
+ ),
40
+ title: "Lorem ipsum dolor sit amet",
41
+ subtitle:
42
+ "Lorem ipsum dolor sit amet, consectetur adipiscing elit. Morbi eleifend at libero in tristique. Pellentesque bibendum arcu eget augue commodo, non convallis eros porttitor",
43
+ actions,
44
+ };
@@ -0,0 +1,76 @@
1
+ import React, { ReactElement } from "react";
2
+ import { render, screen } from "../../tests";
3
+ import userEvent from "@testing-library/user-event";
4
+ import { Placeholder, PlaceholderAction } from "./placeholder";
5
+ import { actions as actionData } from "./placeholder.mock";
6
+ import SearchIcon from "@mui/icons-material/Search";
7
+
8
+ const actions = actionData.map((a) => ({ ...a, onClick: jest.fn() }));
9
+
10
+ describe("Placeholder", () => {
11
+ const renderComponent = ({
12
+ actions = undefined,
13
+ icon = undefined,
14
+ }: { actions?: PlaceholderAction[]; icon?: ReactElement } = {}) => {
15
+ return render(
16
+ <Placeholder
17
+ title="Lorem ipsum"
18
+ subtitle="Lorem ipsum sit amet"
19
+ actions={actions}
20
+ icon={() => icon}
21
+ />,
22
+ );
23
+ };
24
+
25
+ it("would render the title", () => {
26
+ renderComponent();
27
+
28
+ expect(screen.getByRole("heading", { name: /lorem ipsum/i, level: 1 })).toBeInTheDocument();
29
+ });
30
+
31
+ it("would render the subtitle", () => {
32
+ renderComponent();
33
+
34
+ expect(
35
+ screen.getByRole("heading", { name: /lorem ipsum sit amet/i, level: 2 }),
36
+ ).toBeInTheDocument();
37
+ });
38
+
39
+ describe("icon", () => {
40
+ it("wouldn't render an icon if it's not set", () => {
41
+ renderComponent({ icon: undefined });
42
+
43
+ expect(screen.queryByTestId("SearchIcon")).not.toBeInTheDocument();
44
+ });
45
+
46
+ it("would render an icon if it's set", () => {
47
+ renderComponent({ icon: <SearchIcon /> });
48
+
49
+ expect(screen.queryByTestId("SearchIcon")).toBeInTheDocument();
50
+ });
51
+ });
52
+
53
+ describe("actions", () => {
54
+ it("wouldn't render an action if they are not set", () => {
55
+ renderComponent({ actions: undefined });
56
+
57
+ expect(screen.queryByRole("button")).not.toBeInTheDocument();
58
+ });
59
+
60
+ it("would render a button for each action", () => {
61
+ renderComponent({ actions });
62
+
63
+ expect(screen.queryAllByRole("button")).toHaveLength(actions.length);
64
+ expect(screen.queryByRole("button", { name: /add/i })).toBeInTheDocument();
65
+ expect(screen.queryByRole("button", { name: /edit/i })).toBeInTheDocument();
66
+ });
67
+
68
+ it("would call onClick if a button is clicked", async () => {
69
+ renderComponent({ actions });
70
+
71
+ await userEvent.click(screen.getByRole("button", { name: /edit/i }));
72
+
73
+ expect(actions[1].onClick).toHaveBeenCalledTimes(1);
74
+ });
75
+ });
76
+ });
@@ -0,0 +1,75 @@
1
+ import React, { ReactElement } from "react";
2
+ import { Box, Typography, Button } from "@mui/material";
3
+
4
+ type IconColor =
5
+ | "inherit"
6
+ | "action"
7
+ | "disabled"
8
+ | "primary"
9
+ | "secondary"
10
+ | "error"
11
+ | "info"
12
+ | "success"
13
+ | "warning";
14
+
15
+ export interface PlaceholderAction {
16
+ id: string;
17
+ text: string;
18
+ href?: string;
19
+ onClick?: () => void;
20
+ }
21
+
22
+ export interface PlaceholderIconArgs {
23
+ size: number;
24
+ color: IconColor;
25
+ }
26
+
27
+ export interface PlaceholderProps {
28
+ title: string;
29
+ subtitle: string;
30
+ iconSize?: number;
31
+ icon?: ({ size, color }: PlaceholderIconArgs) => ReactElement;
32
+ actions?: PlaceholderAction[];
33
+ }
34
+
35
+ export const Placeholder = ({
36
+ title,
37
+ subtitle,
38
+ icon,
39
+ iconSize = 200,
40
+ actions,
41
+ }: PlaceholderProps) => {
42
+ return (
43
+ <Box
44
+ display="flex"
45
+ flexDirection="column"
46
+ justifyContent="center"
47
+ alignItems="center"
48
+ textAlign="center"
49
+ >
50
+ {icon && icon({ size: iconSize, color: "primary" })}
51
+ <Typography variant="h4" role="heading" aria-level={1}>
52
+ {title}
53
+ </Typography>
54
+ <Typography variant="subtitle1" role="heading" aria-level={2} sx={{ mt: 2 }}>
55
+ {subtitle}
56
+ </Typography>
57
+ {actions && (
58
+ <Box sx={{ pt: 2 }}>
59
+ {actions.map(({ id, text, href, onClick }, index) => (
60
+ <Button
61
+ key={id}
62
+ role="button"
63
+ variant="contained"
64
+ href={href}
65
+ onClick={onClick}
66
+ sx={{ mr: index < actions.length - 1 ? 2 : 0 }}
67
+ >
68
+ {text}
69
+ </Button>
70
+ ))}
71
+ </Box>
72
+ )}
73
+ </Box>
74
+ );
75
+ };
@@ -0,0 +1 @@
1
+ export * from "./query-container";