@agilant/toga-blox 1.0.5
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.
- package/Dockerfile +9 -0
- package/README.md +69 -0
- package/assets/Logo.png +0 -0
- package/assets/compass-card-image-2.png +0 -0
- package/assets/compass-card-image-3.png +0 -0
- package/assets/compass-card-image-4.png +0 -0
- package/assets/compass-card-image.png +0 -0
- package/assets/compass-logo.png +0 -0
- package/assets/compass-tech-hero-bg.png +0 -0
- package/assets/contact-image.png +0 -0
- package/assets/green-laptop.png +0 -0
- package/assets/heroImage.png +0 -0
- package/assets/placeholder-no-image-available.png +0 -0
- package/assets/team.png +0 -0
- package/declarations.d.ts +4 -0
- package/docker-compose.yml +22 -0
- package/global.css +4 -0
- package/index.js +4 -0
- package/nodemon.json +5 -0
- package/package.json +70 -0
- package/postcss.config.js +6 -0
- package/src/components/Badge/Badge.stories.tsx +284 -0
- package/src/components/Badge/Badge.test.tsx +185 -0
- package/src/components/Badge/Badge.tsx +137 -0
- package/src/components/Badge/Badge.types.tsx +28 -0
- package/src/components/Badge/badgeClassNames.tsx +152 -0
- package/src/components/Badge/index.ts +2 -0
- package/src/components/Card/Card.stories.tsx +91 -0
- package/src/components/Card/Card.test.tsx +53 -0
- package/src/components/Card/Card.tsx +30 -0
- package/src/components/Card/Card.types.ts +11 -0
- package/src/components/Card/DUMMYPRODUCTDATA.json +670 -0
- package/src/components/Card/cardClassNames.ts +49 -0
- package/src/components/Card/index.ts +3 -0
- package/src/components/Card/templates/CompassCardTemplate.tsx +58 -0
- package/src/components/Card/templates/HorizontalCardTemplate.tsx +184 -0
- package/src/components/Card/templates/VerticalCardTemplate.tsx +154 -0
- package/src/components/Footer/ContactInfoItem.tsx +20 -0
- package/src/components/Footer/DUMMYFOOTERDATA.json +132 -0
- package/src/components/Footer/Footer.stories.tsx +292 -0
- package/src/components/Footer/Footer.test.tsx +90 -0
- package/src/components/Footer/Footer.tsx +159 -0
- package/src/components/Footer/Footer.types.tsx +61 -0
- package/src/components/Footer/footerClassNames.tsx +57 -0
- package/src/components/FormButton/FormButton.stories.tsx +199 -0
- package/src/components/FormButton/FormButton.test.tsx +73 -0
- package/src/components/FormButton/FormButton.tsx +116 -0
- package/src/components/FormButton/FormButton.types.ts +32 -0
- package/src/components/FormButton/formButtonClassNames.tsx +153 -0
- package/src/components/FormButton/index.ts +2 -0
- package/src/components/GenericList/DUMMYLISTDATA.json +560 -0
- package/src/components/GenericList/GenericList.stories.tsx +104 -0
- package/src/components/GenericList/GenericList.test.tsx +29 -0
- package/src/components/GenericList/GenericList.tsx +146 -0
- package/src/components/GenericList/genericListClassNames.tsx +8 -0
- package/src/components/GenericList/templates/DummyDataList.tsx +23 -0
- package/src/components/GenericList/templates/DynamicIconList.tsx +74 -0
- package/src/components/HamburgerButton/HamburgerButton.tsx +68 -0
- package/src/components/HamburgerButton/HamburgerButton.types.tsx +6 -0
- package/src/components/HamburgerButton/index.ts +2 -0
- package/src/components/Header/DUMMYICONDATA.json +136 -0
- package/src/components/Header/Header.stories.tsx +521 -0
- package/src/components/Header/Header.test.tsx +323 -0
- package/src/components/Header/Header.tsx +289 -0
- package/src/components/Header/Header.types.ts +52 -0
- package/src/components/Header/headerClassNames.tsx +50 -0
- package/src/components/Header/headerContext.tsx +125 -0
- package/src/components/Header/index.ts +2 -0
- package/src/components/Hero/Hero.stories.tsx +69 -0
- package/src/components/Hero/Hero.test.tsx +109 -0
- package/src/components/Hero/Hero.tsx +58 -0
- package/src/components/Hero/Hero.types.ts +9 -0
- package/src/components/Hero/index.ts +2 -0
- package/src/components/Icon/Icon.stories.tsx +227 -0
- package/src/components/Icon/Icon.test.tsx +53 -0
- package/src/components/Icon/Icon.tsx +208 -0
- package/src/components/Icon/Icon.types.ts +24 -0
- package/src/components/Icon/iconClassNames.ts +79 -0
- package/src/components/Icon/index.ts +2 -0
- package/src/components/Image/Image.stories.tsx +79 -0
- package/src/components/Image/Image.test.tsx +87 -0
- package/src/components/Image/Image.tsx +49 -0
- package/src/components/Image/Image.types.ts +11 -0
- package/src/components/Image/index.ts +2 -0
- package/src/components/Input/Input.stories.tsx +651 -0
- package/src/components/Input/Input.test.tsx +90 -0
- package/src/components/Input/Input.tsx +226 -0
- package/src/components/Input/Input.types.ts +52 -0
- package/src/components/Input/InputMemoTypes.tsx +32 -0
- package/src/components/Input/index.ts +2 -0
- package/src/components/Input/inputClassNames.tsx +169 -0
- package/src/components/MobileMenu/MobileMenu.tsx +41 -0
- package/src/components/MobileMenu/MobileMenu.types.tsx +30 -0
- package/src/components/MobileMenu/index.ts +2 -0
- package/src/components/Nav/DUMMYNAVDATA.json +234 -0
- package/src/components/Nav/Nav.stories.tsx +181 -0
- package/src/components/Nav/Nav.test.tsx +89 -0
- package/src/components/Nav/Nav.tsx +242 -0
- package/src/components/Nav/Nav.types.tsx +35 -0
- package/src/components/Nav/index.ts +2 -0
- package/src/components/Nav/navClassNames.tsx +192 -0
- package/src/components/Page/TableDataDummy.tsx +216 -0
- package/src/components/Page/ViewPageTemplate.stories.tsx +546 -0
- package/src/components/Page/ViewPageTemplate.test.tsx +361 -0
- package/src/components/Page/ViewPageTemplate.tsx +10 -0
- package/src/components/Page/ViewPageTemplate.types.ts +6 -0
- package/src/components/Page/index.ts +2 -0
- package/src/components/PageSection/PageSection.stories.tsx +114 -0
- package/src/components/PageSection/PageSection.tsx +12 -0
- package/src/components/PageSection/PageSection.types.ts +6 -0
- package/src/components/PageSection/PageSections.test.tsx +88 -0
- package/src/components/PageSection/index.ts +2 -0
- package/src/components/Text/Text.stories.tsx +60 -0
- package/src/components/Text/Text.test.tsx +52 -0
- package/src/components/Text/Text.tsx +80 -0
- package/src/components/Text/Text.types.ts +12 -0
- package/src/components/Text/index.ts +2 -0
- package/src/components/Toaster/Toaster.stories.tsx +122 -0
- package/src/components/Toaster/Toaster.test.tsx +61 -0
- package/src/components/Toaster/Toaster.tsx +80 -0
- package/src/components/Toaster/Toaster.types.ts +12 -0
- package/src/components/Toaster/index.ts +2 -0
- package/src/hoc/index.ts +2 -0
- package/src/hoc/styling/withStoryBook.tsx +19 -0
- package/src/main.css +3 -0
- package/src/setupTests.ts +1 -0
- package/src/userHoc/index.ts +1 -0
- package/src/userHoc/withMemo.tsx +20 -0
- package/src/utils/assertTagName.tsx +7 -0
- package/src/utils/generateAccordionItem.tsx +102 -0
- package/src/utils/generateFooterContacts.tsx +75 -0
- package/src/utils/generateNavMenu.tsx +54 -0
- package/src/utils/generateSocialList.tsx +34 -0
- package/src/utils/getFontAwesomeIcon.tsx +25 -0
- package/src/utils/inputValidation.tsx +26 -0
- package/tailwind.config.js +32 -0
- package/tsconfig.json +25 -0
- package/vite.config.ts +33 -0
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
import { render, screen } from "@testing-library/react";
|
|
2
|
+
import { describe, expect, beforeEach, test } from "vitest";
|
|
3
|
+
import Text from "./Text";
|
|
4
|
+
|
|
5
|
+
describe("<Text />", () => {
|
|
6
|
+
beforeEach(() => {
|
|
7
|
+
render(
|
|
8
|
+
<Text
|
|
9
|
+
text="Testing"
|
|
10
|
+
size="md"
|
|
11
|
+
color="red"
|
|
12
|
+
tag="h1"
|
|
13
|
+
fontFamily="sans"
|
|
14
|
+
/>
|
|
15
|
+
);
|
|
16
|
+
});
|
|
17
|
+
|
|
18
|
+
test("renders Text component", () => {
|
|
19
|
+
expect(screen.getByTestId("text")).toBeInTheDocument();
|
|
20
|
+
});
|
|
21
|
+
|
|
22
|
+
test("contain correct text", () => {
|
|
23
|
+
expect(screen.getByTestId("text")).toHaveTextContent("Testing");
|
|
24
|
+
});
|
|
25
|
+
|
|
26
|
+
// Style-based tests might need adjustments
|
|
27
|
+
|
|
28
|
+
// test("renders correct font color", () => {
|
|
29
|
+
// // Adjust the test based on how the color is applied
|
|
30
|
+
// // For example, if using Tailwind, check for the relevant class instead
|
|
31
|
+
// const textElement = screen.getByTestId("text");
|
|
32
|
+
// expect(textElement).toHaveClass("text-red-500");
|
|
33
|
+
// });
|
|
34
|
+
|
|
35
|
+
FIXME: test("renders correct font size", () => {
|
|
36
|
+
// Check for class or style attribute
|
|
37
|
+
const textElement = screen.getByTestId("text");
|
|
38
|
+
expect(textElement).toHaveClass("text-md"); // Or the specific class for medium size
|
|
39
|
+
});
|
|
40
|
+
|
|
41
|
+
test("renders correct font family", () => {
|
|
42
|
+
const textElement = screen.getByTestId("text");
|
|
43
|
+
// Check for the presence of the Tailwind CSS class for 'sans' font family
|
|
44
|
+
expect(textElement).toHaveClass("font-sans");
|
|
45
|
+
});
|
|
46
|
+
|
|
47
|
+
test("renders correct element type", () => {
|
|
48
|
+
// Assuming 'as' prop is set to 'h1' in your test setup
|
|
49
|
+
const textElement = screen.getByTestId("text");
|
|
50
|
+
expect(textElement.tagName).toBe("H1"); // Check that the element is an 'h1'
|
|
51
|
+
});
|
|
52
|
+
});
|
|
@@ -0,0 +1,80 @@
|
|
|
1
|
+
import React, { ReactNode } from "react";
|
|
2
|
+
import { TextTypes } from "./Text.types";
|
|
3
|
+
|
|
4
|
+
const sizeClassesMap = {
|
|
5
|
+
xs: "text-xs",
|
|
6
|
+
sm: "text-sm",
|
|
7
|
+
md: "text-md",
|
|
8
|
+
lg: "text-lg",
|
|
9
|
+
xl: "text-xl",
|
|
10
|
+
xxl: "text-2xl",
|
|
11
|
+
xxxl: "text-3xl",
|
|
12
|
+
};
|
|
13
|
+
|
|
14
|
+
const colorClassesMap: { [key: string]: string } = {
|
|
15
|
+
slate: "text-slate-950",
|
|
16
|
+
red: "text-red-800",
|
|
17
|
+
blue: "text-blue-800",
|
|
18
|
+
green: "text-teal-800",
|
|
19
|
+
black: "text-slate-950",
|
|
20
|
+
white: "text-slate-100",
|
|
21
|
+
};
|
|
22
|
+
|
|
23
|
+
const fontFamilyMap = {
|
|
24
|
+
sans: "font-sans",
|
|
25
|
+
serif: "font-serif",
|
|
26
|
+
mono: "font-mono",
|
|
27
|
+
};
|
|
28
|
+
|
|
29
|
+
const Text: React.FC<TextTypes> = ({
|
|
30
|
+
size,
|
|
31
|
+
color = "slate",
|
|
32
|
+
tag = "span",
|
|
33
|
+
text,
|
|
34
|
+
fontFamily = "sans",
|
|
35
|
+
additionalClasses,
|
|
36
|
+
...props
|
|
37
|
+
}) => {
|
|
38
|
+
const textSizeClasses = sizeClassesMap[size as keyof typeof sizeClassesMap];
|
|
39
|
+
const textColorClasses = colorClassesMap[color];
|
|
40
|
+
const fontFamilyClasses =
|
|
41
|
+
fontFamilyMap[fontFamily as keyof typeof fontFamilyMap];
|
|
42
|
+
|
|
43
|
+
let textClasses = `
|
|
44
|
+
${additionalClasses}
|
|
45
|
+
${textSizeClasses}
|
|
46
|
+
${textColorClasses}
|
|
47
|
+
${fontFamilyClasses}
|
|
48
|
+
|
|
49
|
+
`;
|
|
50
|
+
|
|
51
|
+
// Fixing the TS error on TagName w/ adding type string triggered this error on ElementTag:
|
|
52
|
+
// The error message is indicating that a string value is not assignable to
|
|
53
|
+
// the type React.ElementType. This is because React.ElementType is expecting
|
|
54
|
+
// a React component, not a string. However, in React, a string can be used
|
|
55
|
+
// as a component if it represents a valid HTML tag.
|
|
56
|
+
|
|
57
|
+
// The issue here is that TypeScript is not correctly inferring the type of tag.
|
|
58
|
+
// You can solve this by explicitly telling TypeScript that tag can be a string
|
|
59
|
+
// representing a valid HTML tag or a React component. Here's how you can do it:
|
|
60
|
+
// This tells TypeScript that tag can be a string representing a valid HTML tag
|
|
61
|
+
// (like "span", "div", etc.) or a React component. This should solve your issue.
|
|
62
|
+
// If it doesn't, please provide more context or check the rest of
|
|
63
|
+
// your code for potential type mismatches.
|
|
64
|
+
|
|
65
|
+
// Solution:
|
|
66
|
+
const ElementTag: React.ElementType = tag as
|
|
67
|
+
| keyof JSX.IntrinsicElements
|
|
68
|
+
| React.ComponentType;
|
|
69
|
+
|
|
70
|
+
// Was previously:
|
|
71
|
+
// const ElementTag = tag ?? "span";
|
|
72
|
+
|
|
73
|
+
return (
|
|
74
|
+
<ElementTag data-testid="text" className={`${textClasses}`} {...props}>
|
|
75
|
+
{text}
|
|
76
|
+
</ElementTag>
|
|
77
|
+
);
|
|
78
|
+
};
|
|
79
|
+
|
|
80
|
+
export default Text;
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
import { ReactNode } from "react";
|
|
2
|
+
|
|
3
|
+
export type TagName = "span" | "p" | "h1" | "h2" | "h3" | "h4" | "h5" | string;
|
|
4
|
+
|
|
5
|
+
export interface TextTypes {
|
|
6
|
+
size: string;
|
|
7
|
+
color: "red" | "blue" | "green" | "black" | "slate" | string;
|
|
8
|
+
tag?: TagName;
|
|
9
|
+
text: ReactNode;
|
|
10
|
+
fontFamily: "sans" | "mono" | "serif" | string;
|
|
11
|
+
additionalClasses?: string | undefined;
|
|
12
|
+
}
|
|
@@ -0,0 +1,122 @@
|
|
|
1
|
+
import React from "react";
|
|
2
|
+
|
|
3
|
+
import Toaster, { ToasterTypes } from ".";
|
|
4
|
+
import { Meta, StoryFn } from "@storybook/react";
|
|
5
|
+
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
|
|
6
|
+
import { faCheck, faExclamation, faX } from "@fortawesome/free-solid-svg-icons";
|
|
7
|
+
|
|
8
|
+
export default {
|
|
9
|
+
title: "Components/Toaster",
|
|
10
|
+
component: Toaster,
|
|
11
|
+
argTypes: {
|
|
12
|
+
color: {
|
|
13
|
+
control: "select",
|
|
14
|
+
description: "The main color displayed on the toaster",
|
|
15
|
+
options: ["green-800", "red-800", "blue-800", "orange-800"],
|
|
16
|
+
},
|
|
17
|
+
|
|
18
|
+
icon: {
|
|
19
|
+
table: {
|
|
20
|
+
disable: true,
|
|
21
|
+
},
|
|
22
|
+
},
|
|
23
|
+
title: {
|
|
24
|
+
table: {
|
|
25
|
+
disable: true,
|
|
26
|
+
},
|
|
27
|
+
},
|
|
28
|
+
message: {
|
|
29
|
+
control: "text",
|
|
30
|
+
description: "The main text displayed on the toaster",
|
|
31
|
+
},
|
|
32
|
+
additionalClasses: {
|
|
33
|
+
table: {
|
|
34
|
+
disable: true,
|
|
35
|
+
},
|
|
36
|
+
},
|
|
37
|
+
clearText: {
|
|
38
|
+
control: "text",
|
|
39
|
+
description:
|
|
40
|
+
"The text displayed on the clear message button if set to true",
|
|
41
|
+
},
|
|
42
|
+
hasClearText: {
|
|
43
|
+
control: "boolean",
|
|
44
|
+
description: "must be set to true to display the clear text button",
|
|
45
|
+
},
|
|
46
|
+
fullColor: {
|
|
47
|
+
control: "boolean",
|
|
48
|
+
description: "toggle the background color of toaster",
|
|
49
|
+
},
|
|
50
|
+
hasBumper: {
|
|
51
|
+
control: "boolean",
|
|
52
|
+
description: "toggle the left bumper toaster",
|
|
53
|
+
},
|
|
54
|
+
clearMessage: {
|
|
55
|
+
table: {
|
|
56
|
+
disable: true,
|
|
57
|
+
},
|
|
58
|
+
},
|
|
59
|
+
},
|
|
60
|
+
|
|
61
|
+
tags: ["autodocs"],
|
|
62
|
+
parameters: {
|
|
63
|
+
// Optional parameter to center the component in the Canvas. More info: https://storybook.js.org/docs/configure/story-layout
|
|
64
|
+
layout: "centered",
|
|
65
|
+
},
|
|
66
|
+
} as Meta;
|
|
67
|
+
|
|
68
|
+
const minWidth = "min-w-[200px]";
|
|
69
|
+
|
|
70
|
+
const Template: StoryFn<ToasterTypes> = (args) => (
|
|
71
|
+
<div className="">
|
|
72
|
+
<Toaster {...args} />
|
|
73
|
+
</div>
|
|
74
|
+
);
|
|
75
|
+
|
|
76
|
+
export const Default = Template.bind({});
|
|
77
|
+
Default.args = {
|
|
78
|
+
message: "Your action was successful!",
|
|
79
|
+
additionalClasses: `border-blue-500 ${minWidth}`,
|
|
80
|
+
};
|
|
81
|
+
|
|
82
|
+
export const WithIconFail = Template.bind({});
|
|
83
|
+
WithIconFail.args = {
|
|
84
|
+
...Default.args,
|
|
85
|
+
message: "Your action failed!",
|
|
86
|
+
color: "red-800",
|
|
87
|
+
icon: <FontAwesomeIcon icon={faX} />,
|
|
88
|
+
additionalClasses: `border-red-500 ${minWidth}`,
|
|
89
|
+
};
|
|
90
|
+
|
|
91
|
+
export const WithIconSuccess = Template.bind({});
|
|
92
|
+
WithIconSuccess.args = {
|
|
93
|
+
...Default.args,
|
|
94
|
+
color: "green-800",
|
|
95
|
+
icon: <FontAwesomeIcon icon={faCheck} />,
|
|
96
|
+
additionalClasses: `border-green-500 ${minWidth}`,
|
|
97
|
+
};
|
|
98
|
+
|
|
99
|
+
export const WithIconAlert = Template.bind({});
|
|
100
|
+
WithIconAlert.args = {
|
|
101
|
+
...Default.args,
|
|
102
|
+
message: "Alert, are you sure!",
|
|
103
|
+
color: "orange-800",
|
|
104
|
+
icon: <FontAwesomeIcon icon={faExclamation} />,
|
|
105
|
+
additionalClasses: `border-orange-500 ${minWidth}`,
|
|
106
|
+
};
|
|
107
|
+
|
|
108
|
+
export const HasClearMessage = Template.bind({});
|
|
109
|
+
HasClearMessage.args = {
|
|
110
|
+
...Default.args,
|
|
111
|
+
clearText: "Clear Message",
|
|
112
|
+
color: "blue-800",
|
|
113
|
+
hasClearText: true,
|
|
114
|
+
clearMessage: () => alert("Message Cleared!"),
|
|
115
|
+
};
|
|
116
|
+
|
|
117
|
+
export const FullColor = Template.bind({});
|
|
118
|
+
FullColor.args = {
|
|
119
|
+
...Default.args,
|
|
120
|
+
fullColor: true,
|
|
121
|
+
color: "blue-800",
|
|
122
|
+
};
|
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
import { render, screen, fireEvent } from "@testing-library/react";
|
|
2
|
+
import { describe, expect, beforeEach, test, vi } from "vitest";
|
|
3
|
+
import Toaster from "./Toaster";
|
|
4
|
+
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
|
|
5
|
+
import { faCoffee, faTimes } from "@fortawesome/free-solid-svg-icons";
|
|
6
|
+
|
|
7
|
+
describe("Toaster Component Tests", () => {
|
|
8
|
+
test("renders correctly with minimal props", () => {
|
|
9
|
+
render(<Toaster message="Test Message" color="green-500" />);
|
|
10
|
+
expect(screen.getByTestId("toaster")).toHaveTextContent("Test Message");
|
|
11
|
+
expect(screen.getByTestId("toaster")).toHaveClass("border-green-500");
|
|
12
|
+
});
|
|
13
|
+
|
|
14
|
+
test("renders with an icon when provided", () => {
|
|
15
|
+
render(
|
|
16
|
+
<Toaster
|
|
17
|
+
message="Test Message"
|
|
18
|
+
color="blue-500"
|
|
19
|
+
icon={<FontAwesomeIcon icon={faCoffee} />}
|
|
20
|
+
/>
|
|
21
|
+
);
|
|
22
|
+
expect(screen.getByTestId("toaster")).toBeInTheDocument();
|
|
23
|
+
});
|
|
24
|
+
|
|
25
|
+
test("renders the title when provided", () => {
|
|
26
|
+
render(
|
|
27
|
+
<Toaster
|
|
28
|
+
message="Test Message"
|
|
29
|
+
color="red-500"
|
|
30
|
+
title="Test Title"
|
|
31
|
+
/>
|
|
32
|
+
);
|
|
33
|
+
expect(screen.getByTestId("toaster")).toHaveTextContent("Test Title");
|
|
34
|
+
});
|
|
35
|
+
|
|
36
|
+
test("renders with full color background", () => {
|
|
37
|
+
render(
|
|
38
|
+
<Toaster
|
|
39
|
+
message="Test Message"
|
|
40
|
+
color="blue-500"
|
|
41
|
+
title="Test Title"
|
|
42
|
+
fullColor
|
|
43
|
+
/>
|
|
44
|
+
);
|
|
45
|
+
expect(screen.getByTestId("toaster")).toHaveClass("bg-blue-500");
|
|
46
|
+
});
|
|
47
|
+
|
|
48
|
+
test("clears the message on clear icon click", async () => {
|
|
49
|
+
const clearMessage = vi.fn();
|
|
50
|
+
render(
|
|
51
|
+
<Toaster
|
|
52
|
+
message="Test Message"
|
|
53
|
+
color="orange-500"
|
|
54
|
+
clearMessage={clearMessage}
|
|
55
|
+
/>
|
|
56
|
+
);
|
|
57
|
+
const clearButton = screen.getByTestId("clearMessage");
|
|
58
|
+
fireEvent.click(clearButton);
|
|
59
|
+
expect(clearMessage).toHaveBeenCalled();
|
|
60
|
+
});
|
|
61
|
+
});
|
|
@@ -0,0 +1,80 @@
|
|
|
1
|
+
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
|
|
2
|
+
import React from "react";
|
|
3
|
+
import Button from "../FormButton/FormButton";
|
|
4
|
+
import { faTimes } from "@fortawesome/free-solid-svg-icons";
|
|
5
|
+
import { ToasterTypes } from ".";
|
|
6
|
+
|
|
7
|
+
const Toaster: React.FC<ToasterTypes> = ({
|
|
8
|
+
message,
|
|
9
|
+
additionalClasses = "",
|
|
10
|
+
clearMessage,
|
|
11
|
+
color,
|
|
12
|
+
icon,
|
|
13
|
+
fullColor = false,
|
|
14
|
+
hasBumper = true,
|
|
15
|
+
title = "",
|
|
16
|
+
hasClearText,
|
|
17
|
+
clearText,
|
|
18
|
+
}) => {
|
|
19
|
+
const handleClearMessage = () => {
|
|
20
|
+
if (clearMessage) {
|
|
21
|
+
clearMessage(true);
|
|
22
|
+
}
|
|
23
|
+
};
|
|
24
|
+
|
|
25
|
+
// Simplified Color Handling
|
|
26
|
+
const iconColorMap: { [key: string]: string } = {
|
|
27
|
+
"green-800": "text-green-800",
|
|
28
|
+
"red-800": "text-red-800",
|
|
29
|
+
"orange-800": "text-orange-800",
|
|
30
|
+
"blue-800": "text-blue-800",
|
|
31
|
+
};
|
|
32
|
+
const iconClass = iconColorMap[color] || "";
|
|
33
|
+
// Improved CSS Class Composition
|
|
34
|
+
const borderClass = `border ${
|
|
35
|
+
hasBumper ? `border-l-4 border-${color}` : "border-l-1"
|
|
36
|
+
} `;
|
|
37
|
+
const backgroundColorClass = fullColor ? `bg-${color}` : "bg-white";
|
|
38
|
+
|
|
39
|
+
const textColorClass = fullColor ? "text-white" : "text-black";
|
|
40
|
+
|
|
41
|
+
return (
|
|
42
|
+
<div
|
|
43
|
+
data-testid="toaster"
|
|
44
|
+
className={`${borderClass} border-${color} p-4 shadow-lg shadow-boxShadow text-sm ${backgroundColorClass} rounded ${additionalClasses}`}
|
|
45
|
+
>
|
|
46
|
+
{title ? (
|
|
47
|
+
<h3 className="ml-8 font-bold text-xl text-left">{title}</h3>
|
|
48
|
+
) : (
|
|
49
|
+
<></>
|
|
50
|
+
)}
|
|
51
|
+
<div className="flex items-center justify-between">
|
|
52
|
+
<span className={`mr-2 ${iconClass}`}>{icon}</span>
|
|
53
|
+
<span className={textColorClass}>{message}</span>
|
|
54
|
+
{clearMessage && !hasClearText && (
|
|
55
|
+
<span className="cursor-pointer hover:bg-gray-200 py-1 px-2 rounded-md">
|
|
56
|
+
<FontAwesomeIcon
|
|
57
|
+
data-testid="clearMessage"
|
|
58
|
+
onClick={handleClearMessage}
|
|
59
|
+
className={`cursor-pointer ${textColorClass}`}
|
|
60
|
+
icon={faTimes}
|
|
61
|
+
/>
|
|
62
|
+
</span>
|
|
63
|
+
)}
|
|
64
|
+
{hasClearText && clearText && (
|
|
65
|
+
<Button
|
|
66
|
+
onClick={handleClearMessage}
|
|
67
|
+
text={clearText}
|
|
68
|
+
color="primary"
|
|
69
|
+
shape="outline"
|
|
70
|
+
type={"button"}
|
|
71
|
+
isDisabled={false}
|
|
72
|
+
additionalClasses="ml-2 px-4"
|
|
73
|
+
/>
|
|
74
|
+
)}
|
|
75
|
+
</div>
|
|
76
|
+
</div>
|
|
77
|
+
);
|
|
78
|
+
};
|
|
79
|
+
|
|
80
|
+
export default Toaster;
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
export interface ToasterTypes {
|
|
2
|
+
message: string;
|
|
3
|
+
additionalClasses?: string;
|
|
4
|
+
clearMessage?: React.Dispatch<React.SetStateAction<boolean>>;
|
|
5
|
+
color: "green-800" | "red-800" | "yellow-800" | "blue-800" | "orange-800";
|
|
6
|
+
icon?: JSX.Element;
|
|
7
|
+
fullColor?: boolean;
|
|
8
|
+
hasBumper?: boolean;
|
|
9
|
+
title?: string;
|
|
10
|
+
hasClearText?: boolean;
|
|
11
|
+
clearText?: string;
|
|
12
|
+
}
|
package/src/hoc/index.ts
ADDED
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
import React, { CSSProperties } from "react";
|
|
2
|
+
|
|
3
|
+
export interface WithStoryBookProps {
|
|
4
|
+
storybookStyle?: CSSProperties;
|
|
5
|
+
}
|
|
6
|
+
|
|
7
|
+
const withStoryBook = <P extends object>(
|
|
8
|
+
Component: React.ComponentType<P>
|
|
9
|
+
): React.FC<P & WithStoryBookProps> => {
|
|
10
|
+
return ({ storybookStyle, ...props }: WithStoryBookProps) => {
|
|
11
|
+
return (
|
|
12
|
+
<div style={storybookStyle}>
|
|
13
|
+
<Component {...(props as P)} />
|
|
14
|
+
</div>
|
|
15
|
+
);
|
|
16
|
+
};
|
|
17
|
+
};
|
|
18
|
+
|
|
19
|
+
export default withStoryBook;
|
package/src/main.css
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
import "@testing-library/jest-dom";
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export { default as withMemo } from "./withMemo";
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
// withMemo.jsx
|
|
2
|
+
import React, { memo } from "react";
|
|
3
|
+
|
|
4
|
+
// Define a generic type that extends React component props
|
|
5
|
+
type ComponentType<P = {}> = React.ComponentType<P>;
|
|
6
|
+
|
|
7
|
+
// This is the type for the props comparison function
|
|
8
|
+
type ArePropsEqual<P> = (
|
|
9
|
+
prevProps: Readonly<P>,
|
|
10
|
+
nextProps: Readonly<P>
|
|
11
|
+
) => boolean;
|
|
12
|
+
|
|
13
|
+
const withMemo = <P extends {}>(
|
|
14
|
+
Component: ComponentType<P>,
|
|
15
|
+
arePropsEqual?: ArePropsEqual<P>
|
|
16
|
+
) => {
|
|
17
|
+
return memo(Component, arePropsEqual);
|
|
18
|
+
};
|
|
19
|
+
|
|
20
|
+
export default withMemo;
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
import { TagName } from "../components/Text/Text";
|
|
2
|
+
function assertTagName(tag: string): asserts tag is TagName {
|
|
3
|
+
if (!["span", "p", "h1", "h2", "h3", "h4", "h5"].includes(tag)) {
|
|
4
|
+
throw new Error(`Invalid tag name: ${tag}`);
|
|
5
|
+
}
|
|
6
|
+
}
|
|
7
|
+
export default assertTagName;
|
|
@@ -0,0 +1,102 @@
|
|
|
1
|
+
import React, { Fragment, useState } from "react";
|
|
2
|
+
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
|
|
3
|
+
import { faChevronDown, faChevronUp } from "@fortawesome/free-solid-svg-icons";
|
|
4
|
+
|
|
5
|
+
type NavItem = {
|
|
6
|
+
title: string;
|
|
7
|
+
links: { link: string; menuItem: string }[];
|
|
8
|
+
};
|
|
9
|
+
|
|
10
|
+
type AccordionProps = {
|
|
11
|
+
navData: NavItem[];
|
|
12
|
+
accordionParentStyle: string;
|
|
13
|
+
accordionExpandedStyle: string;
|
|
14
|
+
};
|
|
15
|
+
|
|
16
|
+
export const Accordion: React.FC<AccordionProps> = ({
|
|
17
|
+
navData,
|
|
18
|
+
accordionParentStyle,
|
|
19
|
+
accordionExpandedStyle,
|
|
20
|
+
}) => {
|
|
21
|
+
// setting state to be an object, with each key being the index of the accordion item ->
|
|
22
|
+
// allows for multiple accordions to be open at once
|
|
23
|
+
// had to reduce in order for aria-expanded to work
|
|
24
|
+
const [accordionExpanded, setAccordionExpanded] = useState<
|
|
25
|
+
Record<number, boolean>
|
|
26
|
+
>(navData.reduce((acc, _, index) => ({ ...acc, [index]: false }), {}));
|
|
27
|
+
|
|
28
|
+
const toggleAccordion = (index: number) => {
|
|
29
|
+
setAccordionExpanded((prevState) => ({
|
|
30
|
+
...prevState,
|
|
31
|
+
[index]: !prevState[index],
|
|
32
|
+
}));
|
|
33
|
+
};
|
|
34
|
+
|
|
35
|
+
// Expand the accordion when Enter key is pressed for tab users
|
|
36
|
+
const handleKeyDown = (
|
|
37
|
+
event: React.KeyboardEvent<HTMLDivElement>,
|
|
38
|
+
index: number
|
|
39
|
+
) => {
|
|
40
|
+
if (event.key === "Enter") {
|
|
41
|
+
toggleAccordion(index);
|
|
42
|
+
}
|
|
43
|
+
};
|
|
44
|
+
|
|
45
|
+
return (
|
|
46
|
+
<>
|
|
47
|
+
{navData.map((item: NavItem, index: number) => (
|
|
48
|
+
<Fragment key={item.title}>
|
|
49
|
+
{/* SCREEN READER:
|
|
50
|
+
1. should announce expanded status
|
|
51
|
+
2. should announce that parent item is a button
|
|
52
|
+
3. should announce title of accordion
|
|
53
|
+
4. should be able to use enter key to open
|
|
54
|
+
5. should announce that this is a navigation */}
|
|
55
|
+
<div
|
|
56
|
+
className={`cursor-pointer ${accordionParentStyle}`}
|
|
57
|
+
role="button"
|
|
58
|
+
onClick={() => toggleAccordion(index)}
|
|
59
|
+
aria-expanded={accordionExpanded[index]}
|
|
60
|
+
tabIndex={0}
|
|
61
|
+
onKeyDown={(event) => handleKeyDown(event, index)} // Attach handleKeyDown function here
|
|
62
|
+
>
|
|
63
|
+
<div className="px-6 text-left items-center h-10 select-none flex justify-between flex-row">
|
|
64
|
+
<div className="flex w-3/4">{item.title}</div>
|
|
65
|
+
<div className="flex w-1/2 justify-end">
|
|
66
|
+
<FontAwesomeIcon
|
|
67
|
+
className={`text-xs transition-transform duration-300 transform ${
|
|
68
|
+
accordionExpanded[index]
|
|
69
|
+
? "rotate-180"
|
|
70
|
+
: ""
|
|
71
|
+
}`}
|
|
72
|
+
icon={faChevronDown}
|
|
73
|
+
/>
|
|
74
|
+
</div>
|
|
75
|
+
</div>
|
|
76
|
+
</div>
|
|
77
|
+
<div
|
|
78
|
+
className={`px-6 w-full overflow-hidden transition-[max-height] duration-500 ease-in-out ${
|
|
79
|
+
accordionExpanded[index] ? "max-h-96" : "max-h-0"
|
|
80
|
+
}`}
|
|
81
|
+
aria-hidden={!accordionExpanded[index]}
|
|
82
|
+
tabIndex={accordionExpanded[index] ? 0 : -1} // Set tabIndex dynamically
|
|
83
|
+
>
|
|
84
|
+
<ul className={`${accordionExpandedStyle}`}>
|
|
85
|
+
{item.links.map((link) => (
|
|
86
|
+
<li key={link.menuItem} className="py-1">
|
|
87
|
+
<a
|
|
88
|
+
href={link.link}
|
|
89
|
+
className="hover:underline"
|
|
90
|
+
data-testid={link.menuItem}
|
|
91
|
+
>
|
|
92
|
+
{link.menuItem}
|
|
93
|
+
</a>
|
|
94
|
+
</li>
|
|
95
|
+
))}
|
|
96
|
+
</ul>
|
|
97
|
+
</div>
|
|
98
|
+
</Fragment>
|
|
99
|
+
))}
|
|
100
|
+
</>
|
|
101
|
+
);
|
|
102
|
+
};
|
|
@@ -0,0 +1,75 @@
|
|
|
1
|
+
import React from "react";
|
|
2
|
+
import Text from "../components/Text/Text";
|
|
3
|
+
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
|
|
4
|
+
import {
|
|
5
|
+
faEnvelope,
|
|
6
|
+
faLocationDot,
|
|
7
|
+
faPhone,
|
|
8
|
+
} from "@fortawesome/free-solid-svg-icons";
|
|
9
|
+
import { ContactInfoItem } from "../components/Footer/ContactInfoItem";
|
|
10
|
+
|
|
11
|
+
interface ContactProps {
|
|
12
|
+
contactDataList: {
|
|
13
|
+
id: string;
|
|
14
|
+
address: string;
|
|
15
|
+
city: string;
|
|
16
|
+
state: string;
|
|
17
|
+
googleLink: string;
|
|
18
|
+
phone: string;
|
|
19
|
+
email: string;
|
|
20
|
+
}[];
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
export const FooterContacts: React.FC<ContactProps> = ({ contactDataList }) => {
|
|
24
|
+
return contactDataList.map((contact) => {
|
|
25
|
+
const contactDetails = [
|
|
26
|
+
{
|
|
27
|
+
icon: faPhone,
|
|
28
|
+
text: contact.phone,
|
|
29
|
+
},
|
|
30
|
+
{
|
|
31
|
+
icon: faEnvelope,
|
|
32
|
+
text: contact.email,
|
|
33
|
+
},
|
|
34
|
+
];
|
|
35
|
+
|
|
36
|
+
return (
|
|
37
|
+
<div
|
|
38
|
+
key={contact.id}
|
|
39
|
+
data-testid="contact-info"
|
|
40
|
+
className="flex flex-col pt-2 w-60"
|
|
41
|
+
>
|
|
42
|
+
<ContactInfoItem icon={faLocationDot}>
|
|
43
|
+
<a
|
|
44
|
+
className="flex flex-col hover:underline"
|
|
45
|
+
href={contact.googleLink}
|
|
46
|
+
target="_blank"
|
|
47
|
+
>
|
|
48
|
+
{[contact.address, contact.city, contact.state].map(
|
|
49
|
+
(text, index) => (
|
|
50
|
+
<Text
|
|
51
|
+
key={index}
|
|
52
|
+
size={""}
|
|
53
|
+
color={""}
|
|
54
|
+
text={text}
|
|
55
|
+
fontFamily={""}
|
|
56
|
+
/>
|
|
57
|
+
)
|
|
58
|
+
)}
|
|
59
|
+
</a>
|
|
60
|
+
</ContactInfoItem>
|
|
61
|
+
{contactDetails.map((detail, index) => (
|
|
62
|
+
<ContactInfoItem key={index} icon={detail.icon}>
|
|
63
|
+
<Text
|
|
64
|
+
size={""}
|
|
65
|
+
color={""}
|
|
66
|
+
text={detail.text}
|
|
67
|
+
fontFamily={""}
|
|
68
|
+
additionalClasses="my-1"
|
|
69
|
+
/>
|
|
70
|
+
</ContactInfoItem>
|
|
71
|
+
))}
|
|
72
|
+
</div>
|
|
73
|
+
);
|
|
74
|
+
});
|
|
75
|
+
};
|