@unitedstatespowersquadrons/components 1.0.0
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/.github/workflows/main.yml +33 -0
- package/ActionButton.tsx +126 -0
- package/BaseActionButton.tsx +126 -0
- package/Colors.tsx +124 -0
- package/FontAwesomeIcon.tsx +33 -0
- package/IconButton.tsx +87 -0
- package/Readme.md +25 -0
- package/SelectionIconButton.tsx +35 -0
- package/StripedTable/Body.tsx +22 -0
- package/StripedTable/Foot.tsx +22 -0
- package/StripedTable/Head.tsx +22 -0
- package/StripedTable/Table.tsx +27 -0
- package/StripedTable/TableStyles.ts +46 -0
- package/StripedTable/index.tsx +9 -0
- package/Styles.tsx +67 -0
- package/Toasts/Readme.md +110 -0
- package/Toasts/Toast.tsx +162 -0
- package/Toasts/Toasts.tsx +36 -0
- package/Toasts/createDismissToast.ts +16 -0
- package/Toasts/index.tsx +5 -0
- package/Toasts/types.ts +28 -0
- package/eslint.config.mjs +212 -0
- package/index.tsx +30 -0
- package/package.json +33 -0
- package/railsFetchJson.tsx +37 -0
- package/tsconfig.json +30 -0
- package/types.ts +51 -0
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
name: 'Components Library'
|
|
2
|
+
on:
|
|
3
|
+
push:
|
|
4
|
+
paths:
|
|
5
|
+
- '**.ts'
|
|
6
|
+
- '**.tsx'
|
|
7
|
+
- 'eslint.config.mjs'
|
|
8
|
+
- '**.yml'
|
|
9
|
+
- '**.json'
|
|
10
|
+
branches:
|
|
11
|
+
- 'main'
|
|
12
|
+
- '**'
|
|
13
|
+
workflow_dispatch:
|
|
14
|
+
|
|
15
|
+
jobs:
|
|
16
|
+
eslint:
|
|
17
|
+
runs-on: ubuntu-latest
|
|
18
|
+
steps:
|
|
19
|
+
- name: Checkout Code
|
|
20
|
+
uses: actions/checkout@v4
|
|
21
|
+
- name: Install Packages
|
|
22
|
+
run: yarn
|
|
23
|
+
- name: Run Eslint
|
|
24
|
+
run: yarn run eslint
|
|
25
|
+
tsc:
|
|
26
|
+
runs-on: ubuntu-latest
|
|
27
|
+
steps:
|
|
28
|
+
- name: Checkout Code
|
|
29
|
+
uses: actions/checkout@v4
|
|
30
|
+
- name: Install Packages
|
|
31
|
+
run: yarn
|
|
32
|
+
- name: Run TSC
|
|
33
|
+
run: yarn run tsc
|
package/ActionButton.tsx
ADDED
|
@@ -0,0 +1,126 @@
|
|
|
1
|
+
import React from "react";
|
|
2
|
+
import { createUseStyles } from "react-jss";
|
|
3
|
+
import classNames from "classnames";
|
|
4
|
+
import { ButtonColorName } from "./Colors";
|
|
5
|
+
import FontAwesomeIcon, { FontAwesomeProps } from "./FontAwesomeIcon";
|
|
6
|
+
import Styles from "./Styles";
|
|
7
|
+
import BaseActionButton from "./BaseActionButton";
|
|
8
|
+
import { HrefProps, MethodProps, OnClickProps } from "./types";
|
|
9
|
+
|
|
10
|
+
const useStyles = createUseStyles({
|
|
11
|
+
big: { ...Styles.bigText },
|
|
12
|
+
blueColored: { ...Styles.colorizeButton("blue") },
|
|
13
|
+
blueGray: { ...Styles.blue },
|
|
14
|
+
button: { ...Styles.button },
|
|
15
|
+
grayColored: { ...Styles.colorizeButton("gray") },
|
|
16
|
+
grayGray: { ...Styles.gray },
|
|
17
|
+
greenColored: { ...Styles.colorizeButton("green") },
|
|
18
|
+
greenGray: { ...Styles.green },
|
|
19
|
+
noMargin: { marginTop: "0" },
|
|
20
|
+
orangeColored: { ...Styles.colorizeButton("orange") },
|
|
21
|
+
orangeGray: { ...Styles.orange },
|
|
22
|
+
purpleColored: { ...Styles.colorizeButton("purple") },
|
|
23
|
+
purpleGray: { ...Styles.purple },
|
|
24
|
+
redColored: { ...Styles.colorizeButton("red") },
|
|
25
|
+
redGray: { ...Styles.red },
|
|
26
|
+
small: { fontSize: "0.8em" },
|
|
27
|
+
subText: {
|
|
28
|
+
fontSize: "0.75em",
|
|
29
|
+
marginLeft: "0.75em",
|
|
30
|
+
},
|
|
31
|
+
});
|
|
32
|
+
|
|
33
|
+
export interface CoreActionButtonProps extends FontAwesomeProps {
|
|
34
|
+
className?: string | undefined;
|
|
35
|
+
color: ButtonColorName;
|
|
36
|
+
grayMode?: boolean | undefined;
|
|
37
|
+
id?: string;
|
|
38
|
+
size?: "big" | "mediumWithMargin" | "medium" | "small" | undefined;
|
|
39
|
+
subtext?: string;
|
|
40
|
+
text: string;
|
|
41
|
+
title?: string | undefined;
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
export interface ActionButtonHrefProps extends CoreActionButtonProps, HrefProps {}
|
|
45
|
+
export interface ActionButtonMethodProps extends CoreActionButtonProps, MethodProps {}
|
|
46
|
+
export interface ActionButtonOnClickProps extends CoreActionButtonProps, OnClickProps {}
|
|
47
|
+
|
|
48
|
+
export type ActionButtonProps =
|
|
49
|
+
| ActionButtonHrefProps
|
|
50
|
+
| ActionButtonMethodProps
|
|
51
|
+
| ActionButtonOnClickProps;
|
|
52
|
+
|
|
53
|
+
const ActionButton = (props: ActionButtonProps) => {
|
|
54
|
+
const { className, color, fa, grayMode, icon, id, mode, size, style, subtext, text, title } = props;
|
|
55
|
+
const classes = useStyles();
|
|
56
|
+
|
|
57
|
+
const colorClassName = classes[`${color}${grayMode ? "Gray" : "Colored"}`];
|
|
58
|
+
|
|
59
|
+
const sizeClassName = () => {
|
|
60
|
+
switch (size) {
|
|
61
|
+
case "big": return classes.big;
|
|
62
|
+
case "mediumWithMargin": return "";
|
|
63
|
+
case "medium": return classes.noMargin;
|
|
64
|
+
case "small": return classNames(classes.small, classes.noMargin);
|
|
65
|
+
default: return classes.big;
|
|
66
|
+
}
|
|
67
|
+
};
|
|
68
|
+
|
|
69
|
+
const buttonContents = () => {
|
|
70
|
+
return(
|
|
71
|
+
<>
|
|
72
|
+
<FontAwesomeIcon icon={icon} fa={fa} mode={mode} style={style} />
|
|
73
|
+
<span>{text}</span>
|
|
74
|
+
{subtext && <small className={classes.subText}>({subtext})</small>}
|
|
75
|
+
</>
|
|
76
|
+
);
|
|
77
|
+
};
|
|
78
|
+
|
|
79
|
+
if ("onClick" in props) {
|
|
80
|
+
const { confirm, onClick } = props;
|
|
81
|
+
|
|
82
|
+
return(
|
|
83
|
+
<BaseActionButton
|
|
84
|
+
className={classNames(classes.button, colorClassName, sizeClassName(), className)}
|
|
85
|
+
id={id}
|
|
86
|
+
onClick={onClick}
|
|
87
|
+
title={title}
|
|
88
|
+
confirm={confirm}
|
|
89
|
+
>
|
|
90
|
+
{buttonContents()}
|
|
91
|
+
</BaseActionButton>
|
|
92
|
+
);
|
|
93
|
+
} else if ("method" in props) {
|
|
94
|
+
const { confirm, href, method } = props;
|
|
95
|
+
|
|
96
|
+
return(
|
|
97
|
+
<BaseActionButton
|
|
98
|
+
className={classNames(classes.button, colorClassName, sizeClassName(), className)}
|
|
99
|
+
href={href}
|
|
100
|
+
id={id}
|
|
101
|
+
method={method}
|
|
102
|
+
title={title}
|
|
103
|
+
confirm={confirm}
|
|
104
|
+
>
|
|
105
|
+
{buttonContents()}
|
|
106
|
+
</BaseActionButton>
|
|
107
|
+
);
|
|
108
|
+
} else if ("href" in props) {
|
|
109
|
+
const { href } = props;
|
|
110
|
+
|
|
111
|
+
return(
|
|
112
|
+
<BaseActionButton
|
|
113
|
+
className={classNames(classes.button, colorClassName, sizeClassName(), className)}
|
|
114
|
+
href={href}
|
|
115
|
+
id={id}
|
|
116
|
+
title={title}
|
|
117
|
+
>
|
|
118
|
+
{buttonContents()}
|
|
119
|
+
</BaseActionButton>
|
|
120
|
+
);
|
|
121
|
+
} else {
|
|
122
|
+
throw new Error("Invalid ActionButton configuration: must specify props: onClick | (href & method?)");
|
|
123
|
+
}
|
|
124
|
+
};
|
|
125
|
+
|
|
126
|
+
export default ActionButton;
|
|
@@ -0,0 +1,126 @@
|
|
|
1
|
+
import React, { useRef } from "react";
|
|
2
|
+
import { createUseStyles } from "react-jss";
|
|
3
|
+
import classNames from "classnames";
|
|
4
|
+
import { HrefProps, MethodProps, OnClickHandler, OnClickProps } from "./types";
|
|
5
|
+
import { getCsrfTokenHeader } from "./railsFetchJson";
|
|
6
|
+
|
|
7
|
+
const useStyles = createUseStyles({
|
|
8
|
+
button: {
|
|
9
|
+
background: "none",
|
|
10
|
+
border: "none",
|
|
11
|
+
padding: "0",
|
|
12
|
+
},
|
|
13
|
+
});
|
|
14
|
+
|
|
15
|
+
interface CoreBaseActionButtonProps {
|
|
16
|
+
children?: React.ReactNode;
|
|
17
|
+
className?: string | undefined;
|
|
18
|
+
id?: string | undefined;
|
|
19
|
+
title?: string | undefined;
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
export interface BaseActionButtonHrefProps extends CoreBaseActionButtonProps, HrefProps {}
|
|
23
|
+
export interface BaseActionButtonMethodProps extends CoreBaseActionButtonProps, MethodProps {}
|
|
24
|
+
export interface BaseActionButtonOnClickProps extends CoreBaseActionButtonProps, OnClickProps {}
|
|
25
|
+
|
|
26
|
+
export type BaseActionButtonProps =
|
|
27
|
+
| BaseActionButtonHrefProps
|
|
28
|
+
| BaseActionButtonMethodProps
|
|
29
|
+
| BaseActionButtonOnClickProps;
|
|
30
|
+
|
|
31
|
+
const BaseActionButton = (props: BaseActionButtonProps) => {
|
|
32
|
+
const { children, className, id, title } = props;
|
|
33
|
+
const classes = useStyles();
|
|
34
|
+
|
|
35
|
+
const formRef = useRef<HTMLFormElement>(null);
|
|
36
|
+
|
|
37
|
+
if ("onClick" in props) {
|
|
38
|
+
const { onClick } = props;
|
|
39
|
+
|
|
40
|
+
const getConfirmation = () => !props.confirm || confirm(props.confirm);
|
|
41
|
+
|
|
42
|
+
const wrappedOnClick: OnClickHandler = event => {
|
|
43
|
+
event.preventDefault();
|
|
44
|
+
|
|
45
|
+
if (getConfirmation()) onClick(event);
|
|
46
|
+
};
|
|
47
|
+
|
|
48
|
+
return(
|
|
49
|
+
<button
|
|
50
|
+
type="button"
|
|
51
|
+
id={id}
|
|
52
|
+
className={classNames("action-button", classes.button, className)}
|
|
53
|
+
onClick={wrappedOnClick}
|
|
54
|
+
title={title}
|
|
55
|
+
>
|
|
56
|
+
{children}
|
|
57
|
+
</button>
|
|
58
|
+
);
|
|
59
|
+
} else if ("method" in props) {
|
|
60
|
+
const { href, method } = props;
|
|
61
|
+
|
|
62
|
+
const getConfirmation = () => !props.confirm || confirm(props.confirm);
|
|
63
|
+
|
|
64
|
+
const form = () => {
|
|
65
|
+
if (!method) return;
|
|
66
|
+
|
|
67
|
+
return(
|
|
68
|
+
<form action={href} method="POST" ref={formRef}>
|
|
69
|
+
<input type="hidden" name="authenticity_token" value={getCsrfTokenHeader()} />
|
|
70
|
+
<input type="hidden" name="_method" value={method.toUpperCase()} />
|
|
71
|
+
</form>
|
|
72
|
+
);
|
|
73
|
+
};
|
|
74
|
+
|
|
75
|
+
const submitForm: OnClickHandler = event => {
|
|
76
|
+
event.preventDefault();
|
|
77
|
+
|
|
78
|
+
if (!getConfirmation()) return;
|
|
79
|
+
|
|
80
|
+
const methodForm = formRef.current;
|
|
81
|
+
|
|
82
|
+
if (methodForm) methodForm.submit();
|
|
83
|
+
};
|
|
84
|
+
|
|
85
|
+
return(
|
|
86
|
+
<>
|
|
87
|
+
{form()}
|
|
88
|
+
<button
|
|
89
|
+
type="button"
|
|
90
|
+
id={id}
|
|
91
|
+
className={classNames("action-button", classes.button, className)}
|
|
92
|
+
onClick={submitForm}
|
|
93
|
+
title={title}
|
|
94
|
+
>
|
|
95
|
+
{children}
|
|
96
|
+
</button>
|
|
97
|
+
</>
|
|
98
|
+
);
|
|
99
|
+
} else if ("href" in props) {
|
|
100
|
+
const { href, newTab } = props;
|
|
101
|
+
|
|
102
|
+
const onClick = () => {
|
|
103
|
+
if (newTab) {
|
|
104
|
+
open(href, "_blank");
|
|
105
|
+
} else {
|
|
106
|
+
location.href = href;
|
|
107
|
+
}
|
|
108
|
+
};
|
|
109
|
+
|
|
110
|
+
return(
|
|
111
|
+
<button
|
|
112
|
+
type="button"
|
|
113
|
+
onClick={onClick}
|
|
114
|
+
id={id}
|
|
115
|
+
className={classNames("action-button", classes.button, className)}
|
|
116
|
+
title={title}
|
|
117
|
+
>
|
|
118
|
+
{children}
|
|
119
|
+
</button>
|
|
120
|
+
);
|
|
121
|
+
} else {
|
|
122
|
+
throw new Error("Invalid BaseActionButton configuration: must specify props: onClick | (href & method?)");
|
|
123
|
+
}
|
|
124
|
+
};
|
|
125
|
+
|
|
126
|
+
export default BaseActionButton;
|
package/Colors.tsx
ADDED
|
@@ -0,0 +1,124 @@
|
|
|
1
|
+
const baseColors = {
|
|
2
|
+
blue: "blue", // #0000FF
|
|
3
|
+
green: "darkgreen", // #006400
|
|
4
|
+
orange: "#CC6600",
|
|
5
|
+
purple: "#6600CC",
|
|
6
|
+
red: "darkred", // #8B0000
|
|
7
|
+
};
|
|
8
|
+
|
|
9
|
+
const hoverColors = {
|
|
10
|
+
blue: "#0000CC",
|
|
11
|
+
green: "#003300",
|
|
12
|
+
orange: "#99CC00",
|
|
13
|
+
purple: "#330099",
|
|
14
|
+
red: "#660000",
|
|
15
|
+
};
|
|
16
|
+
|
|
17
|
+
const shades = {
|
|
18
|
+
_0: "black",
|
|
19
|
+
_1: "#111111",
|
|
20
|
+
_2: "#222222",
|
|
21
|
+
_3: "#333333",
|
|
22
|
+
_4: "#444444",
|
|
23
|
+
_5: "#555555",
|
|
24
|
+
_6: "#666666",
|
|
25
|
+
_7: "#777777",
|
|
26
|
+
_8: "#888888",
|
|
27
|
+
_9: "#999999",
|
|
28
|
+
_a: "#AAAAAA",
|
|
29
|
+
_b: "#BBBBBB",
|
|
30
|
+
_c: "#CCCCCC",
|
|
31
|
+
_d: "#DDDDDD",
|
|
32
|
+
_e: "#EEEEEE",
|
|
33
|
+
_f: "#FFFFFF",
|
|
34
|
+
};
|
|
35
|
+
|
|
36
|
+
type HexChar =
|
|
37
|
+
| "0" | "1" | "2" | "3" | "4" | "5" | "6" | "7" | "8" | "9"
|
|
38
|
+
| "A" | "B" | "C" | "D" | "E" | "F" | "a" | "b" | "c" | "d" | "e" | "f";
|
|
39
|
+
const shade = (value: number | `${HexChar}${HexChar}`) => {
|
|
40
|
+
const validateNumber = () => {
|
|
41
|
+
if (typeof value !== "number") { return; }
|
|
42
|
+
if (value < 0 || value > 255) { throw new Error(`Shade out of bounds: ${value}`); }
|
|
43
|
+
};
|
|
44
|
+
|
|
45
|
+
const validateString = () => {
|
|
46
|
+
if (typeof value !== "string") { return; }
|
|
47
|
+
if (value.length !== 2) { throw new Error(`Shade too long: "${value}"`); }
|
|
48
|
+
if (/[^0-9a-fA-F]/.exec(value)) { throw new Error(`Shade out of bounds: "${value}"`); }
|
|
49
|
+
};
|
|
50
|
+
|
|
51
|
+
validateNumber();
|
|
52
|
+
validateString();
|
|
53
|
+
|
|
54
|
+
const hex = value.toString(16).padStart(2, "0");
|
|
55
|
+
|
|
56
|
+
return `#${hex.repeat(3)}`;
|
|
57
|
+
};
|
|
58
|
+
|
|
59
|
+
/* eslint-disable sort-keys */
|
|
60
|
+
const shadesLegacy = {
|
|
61
|
+
black: shades._0,
|
|
62
|
+
darkGray: shades._3,
|
|
63
|
+
mediumGray: shades._5,
|
|
64
|
+
gray: shades._6,
|
|
65
|
+
lightGray: shades._9,
|
|
66
|
+
veryLightGray: shades._c,
|
|
67
|
+
paleGray: shades._e,
|
|
68
|
+
};
|
|
69
|
+
/* eslint-enable sort-keys */
|
|
70
|
+
|
|
71
|
+
export type ButtonColorName =
|
|
72
|
+
| "blue"
|
|
73
|
+
| "gray"
|
|
74
|
+
| "green"
|
|
75
|
+
| "orange"
|
|
76
|
+
| "purple"
|
|
77
|
+
| "red";
|
|
78
|
+
|
|
79
|
+
const specificColors = {
|
|
80
|
+
answer: {
|
|
81
|
+
highlighted: "#EEFFFF",
|
|
82
|
+
selected: "#99FFFF",
|
|
83
|
+
},
|
|
84
|
+
button: {
|
|
85
|
+
background: {
|
|
86
|
+
hover: {
|
|
87
|
+
blue: "#5588EE",
|
|
88
|
+
gray: "#BBBBBB",
|
|
89
|
+
green: "#55BB55",
|
|
90
|
+
orange: "#EE8833",
|
|
91
|
+
purple: "#8844EE",
|
|
92
|
+
red: "#CC6666",
|
|
93
|
+
},
|
|
94
|
+
normal: {
|
|
95
|
+
blue: "#6699FF",
|
|
96
|
+
gray: shadesLegacy.veryLightGray,
|
|
97
|
+
green: "#66CC66",
|
|
98
|
+
orange: "#FF9944",
|
|
99
|
+
purple: "#9955FF",
|
|
100
|
+
red: "#DD7777",
|
|
101
|
+
},
|
|
102
|
+
},
|
|
103
|
+
border: "gray",
|
|
104
|
+
shadow: "#AAAAAA",
|
|
105
|
+
},
|
|
106
|
+
exam: {
|
|
107
|
+
dragging: "#00CCCC",
|
|
108
|
+
instructions: {
|
|
109
|
+
other: "#FFEEEE",
|
|
110
|
+
selected: "#EEEEFF",
|
|
111
|
+
},
|
|
112
|
+
},
|
|
113
|
+
};
|
|
114
|
+
|
|
115
|
+
const Colors = {
|
|
116
|
+
...baseColors,
|
|
117
|
+
hover: { ...hoverColors },
|
|
118
|
+
shade,
|
|
119
|
+
shades,
|
|
120
|
+
...shadesLegacy,
|
|
121
|
+
...specificColors,
|
|
122
|
+
};
|
|
123
|
+
|
|
124
|
+
export default Colors;
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
import React from "react";
|
|
2
|
+
import { createUseStyles } from "react-jss";
|
|
3
|
+
import classNames from "classnames";
|
|
4
|
+
|
|
5
|
+
export interface FontAwesomeProps {
|
|
6
|
+
css?: string | undefined;
|
|
7
|
+
fa?: string | undefined;
|
|
8
|
+
icon: string;
|
|
9
|
+
mode?: "classic" | "duotone" | "sharp" | "sharp-duotone" | undefined;
|
|
10
|
+
style?: "solid" | "regular" | "light" | "thin" | "brands" | "kit" | undefined;
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
const useStyles = createUseStyles({
|
|
14
|
+
icon: {
|
|
15
|
+
padding: "0.25em 0.5em",
|
|
16
|
+
},
|
|
17
|
+
});
|
|
18
|
+
|
|
19
|
+
const FontAwesomeIcon = (props: FontAwesomeProps) => {
|
|
20
|
+
const { css, fa, icon, mode, style } = props;
|
|
21
|
+
const classes = useStyles();
|
|
22
|
+
const faClasses = fa?.split(" ").map(c => `fa-${c}`) || [];
|
|
23
|
+
|
|
24
|
+
return (
|
|
25
|
+
<i
|
|
26
|
+
className={classNames(
|
|
27
|
+
classes.icon, `fa-${style || "solid"}`, `fa-${mode || "classic"}`, `fa-${icon}`, css, ...faClasses
|
|
28
|
+
)}
|
|
29
|
+
/>
|
|
30
|
+
);
|
|
31
|
+
};
|
|
32
|
+
|
|
33
|
+
export default FontAwesomeIcon;
|
package/IconButton.tsx
ADDED
|
@@ -0,0 +1,87 @@
|
|
|
1
|
+
import React from "react";
|
|
2
|
+
import { createUseStyles } from "react-jss";
|
|
3
|
+
import classNames from "classnames";
|
|
4
|
+
import FontAwesomeIcon, { FontAwesomeProps } from "./FontAwesomeIcon";
|
|
5
|
+
import BaseActionButton from "./BaseActionButton";
|
|
6
|
+
import { HrefProps, MethodProps, OnClickProps } from "./types";
|
|
7
|
+
import Styles from "./Styles";
|
|
8
|
+
|
|
9
|
+
const useStyles = createUseStyles({
|
|
10
|
+
icon: {
|
|
11
|
+
...Styles.blue,
|
|
12
|
+
cursor: "pointer",
|
|
13
|
+
fontSize: "1.1em",
|
|
14
|
+
},
|
|
15
|
+
});
|
|
16
|
+
|
|
17
|
+
interface CoreIconProps extends FontAwesomeProps {
|
|
18
|
+
className?: string;
|
|
19
|
+
id?: string | undefined;
|
|
20
|
+
title: string;
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
export interface IconButtonHrefProps extends CoreIconProps, HrefProps {}
|
|
24
|
+
export interface IconButtonMethodProps extends CoreIconProps, MethodProps {}
|
|
25
|
+
export interface IconButtonOnClickProps extends CoreIconProps, OnClickProps {}
|
|
26
|
+
|
|
27
|
+
export type IconButtonProps =
|
|
28
|
+
| IconButtonHrefProps
|
|
29
|
+
| IconButtonMethodProps
|
|
30
|
+
| IconButtonOnClickProps;
|
|
31
|
+
|
|
32
|
+
const IconButton = (props: IconButtonProps) => {
|
|
33
|
+
const { className, fa, icon, id, mode, style, title } = props;
|
|
34
|
+
const classes = useStyles();
|
|
35
|
+
|
|
36
|
+
const configuredIcon = <FontAwesomeIcon icon={icon} fa={fa} mode={mode} style={style} css={className} />;
|
|
37
|
+
|
|
38
|
+
const combinedClassName = classNames(classes.icon, className);
|
|
39
|
+
|
|
40
|
+
if ("onClick" in props) {
|
|
41
|
+
const { confirm, onClick } = props;
|
|
42
|
+
|
|
43
|
+
return(
|
|
44
|
+
<BaseActionButton
|
|
45
|
+
className={combinedClassName}
|
|
46
|
+
id={id}
|
|
47
|
+
onClick={onClick}
|
|
48
|
+
title={title}
|
|
49
|
+
confirm={confirm}
|
|
50
|
+
>
|
|
51
|
+
{configuredIcon}
|
|
52
|
+
</BaseActionButton>
|
|
53
|
+
);
|
|
54
|
+
} else if ("method" in props) {
|
|
55
|
+
const { confirm, href, method } = props;
|
|
56
|
+
|
|
57
|
+
return(
|
|
58
|
+
<BaseActionButton
|
|
59
|
+
className={combinedClassName}
|
|
60
|
+
href={href}
|
|
61
|
+
id={id}
|
|
62
|
+
method={method}
|
|
63
|
+
title={title}
|
|
64
|
+
confirm={confirm}
|
|
65
|
+
>
|
|
66
|
+
{configuredIcon}
|
|
67
|
+
</BaseActionButton>
|
|
68
|
+
);
|
|
69
|
+
} else if ("href" in props) {
|
|
70
|
+
const { href } = props;
|
|
71
|
+
|
|
72
|
+
return(
|
|
73
|
+
<BaseActionButton
|
|
74
|
+
className={combinedClassName}
|
|
75
|
+
href={href}
|
|
76
|
+
id={id}
|
|
77
|
+
title={title}
|
|
78
|
+
>
|
|
79
|
+
{configuredIcon}
|
|
80
|
+
</BaseActionButton>
|
|
81
|
+
);
|
|
82
|
+
} else {
|
|
83
|
+
throw new Error("Invalid IconButton configuration: must specify props: onClick | (href & method?)");
|
|
84
|
+
}
|
|
85
|
+
};
|
|
86
|
+
|
|
87
|
+
export default IconButton;
|
package/Readme.md
ADDED
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
# USPS React Components Library
|
|
2
|
+
|
|
3
|
+
[](https://github.com/unitedstatespowersquadrons/components/actions)
|
|
4
|
+
|
|
5
|
+
## Usage
|
|
6
|
+
|
|
7
|
+
```ts
|
|
8
|
+
import * from "@unitedstatespowersquadrons/components";
|
|
9
|
+
```
|
|
10
|
+
|
|
11
|
+
## Developer Setup
|
|
12
|
+
|
|
13
|
+
Install dependencies with
|
|
14
|
+
|
|
15
|
+
```sh
|
|
16
|
+
yarn
|
|
17
|
+
```
|
|
18
|
+
|
|
19
|
+
## Toasts
|
|
20
|
+
|
|
21
|
+
More detailed information is available for [Toasts](Toasts/Readme.md) usage.
|
|
22
|
+
|
|
23
|
+
## Automated Testing
|
|
24
|
+
|
|
25
|
+
GitHub Actions will automatically run `eslint` and `tsc` on branch pushes.
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
import React from "react";
|
|
2
|
+
import { createUseStyles } from "react-jss";
|
|
3
|
+
import classNames from "classnames";
|
|
4
|
+
import IconButton, { IconButtonOnClickProps } from "./IconButton";
|
|
5
|
+
|
|
6
|
+
const useStyles = createUseStyles({
|
|
7
|
+
icon: { cursor: "pointer" },
|
|
8
|
+
});
|
|
9
|
+
|
|
10
|
+
interface Props extends Omit<IconButtonOnClickProps, "icon"> {
|
|
11
|
+
id?: string;
|
|
12
|
+
selected: boolean;
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
const SelectionIconButton = (props: Props) => {
|
|
16
|
+
const { id, onClick, selected } = props;
|
|
17
|
+
const classes = useStyles();
|
|
18
|
+
|
|
19
|
+
const title = selected ? "Remove Selection" : "Add Selection";
|
|
20
|
+
const icon = selected ? "square-minus" : "square-plus";
|
|
21
|
+
const className = selected ? "red" : "green";
|
|
22
|
+
|
|
23
|
+
return(
|
|
24
|
+
<IconButton
|
|
25
|
+
icon={icon}
|
|
26
|
+
id={id}
|
|
27
|
+
onClick={onClick}
|
|
28
|
+
title={title}
|
|
29
|
+
className={classNames(classes.icon, className)}
|
|
30
|
+
mode="duotone"
|
|
31
|
+
/>
|
|
32
|
+
);
|
|
33
|
+
};
|
|
34
|
+
|
|
35
|
+
export default SelectionIconButton;
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
import React from "react";
|
|
2
|
+
import { createUseStyles } from "react-jss";
|
|
3
|
+
import TableStyles from "./TableStyles";
|
|
4
|
+
|
|
5
|
+
const useStyles = createUseStyles({
|
|
6
|
+
tbody: TableStyles.tbody,
|
|
7
|
+
});
|
|
8
|
+
|
|
9
|
+
interface Props {
|
|
10
|
+
children: React.ReactNode;
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
const Body = (props: Props) => {
|
|
14
|
+
const { children } = props;
|
|
15
|
+
const classes = useStyles();
|
|
16
|
+
|
|
17
|
+
return(
|
|
18
|
+
<tbody className={classes.tbody}>{children}</tbody>
|
|
19
|
+
);
|
|
20
|
+
};
|
|
21
|
+
|
|
22
|
+
export default Body;
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
import React from "react";
|
|
2
|
+
import { createUseStyles } from "react-jss";
|
|
3
|
+
import TableStyles from "./TableStyles";
|
|
4
|
+
|
|
5
|
+
const useStyles = createUseStyles({
|
|
6
|
+
tfoot: TableStyles.tfoot,
|
|
7
|
+
});
|
|
8
|
+
|
|
9
|
+
interface Props {
|
|
10
|
+
children: React.ReactNode;
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
const Head = (props: Props) => {
|
|
14
|
+
const { children } = props;
|
|
15
|
+
const classes = useStyles();
|
|
16
|
+
|
|
17
|
+
return(
|
|
18
|
+
<tfoot className={classes.tfoot}>{children}</tfoot>
|
|
19
|
+
);
|
|
20
|
+
};
|
|
21
|
+
|
|
22
|
+
export default Head;
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
import React from "react";
|
|
2
|
+
import { createUseStyles } from "react-jss";
|
|
3
|
+
import TableStyles from "./TableStyles";
|
|
4
|
+
|
|
5
|
+
const useStyles = createUseStyles({
|
|
6
|
+
thead: TableStyles.thead,
|
|
7
|
+
});
|
|
8
|
+
|
|
9
|
+
interface Props {
|
|
10
|
+
children: React.ReactNode;
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
const Head = (props: Props) => {
|
|
14
|
+
const { children } = props;
|
|
15
|
+
const classes = useStyles();
|
|
16
|
+
|
|
17
|
+
return(
|
|
18
|
+
<thead className={classes.thead}>{children}</thead>
|
|
19
|
+
);
|
|
20
|
+
};
|
|
21
|
+
|
|
22
|
+
export default Head;
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
import React from "react";
|
|
2
|
+
import { createUseStyles } from "react-jss";
|
|
3
|
+
import classNames from "classnames";
|
|
4
|
+
import TableStyles from "./TableStyles";
|
|
5
|
+
|
|
6
|
+
const useStyles = createUseStyles({
|
|
7
|
+
table: TableStyles.table,
|
|
8
|
+
});
|
|
9
|
+
|
|
10
|
+
interface Props {
|
|
11
|
+
children: React.ReactNode;
|
|
12
|
+
className?: string;
|
|
13
|
+
id?: string;
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
const Table = (props: Props) => {
|
|
17
|
+
const { children, className, id } = props;
|
|
18
|
+
const classes = useStyles();
|
|
19
|
+
|
|
20
|
+
return(
|
|
21
|
+
<table id={id} className={classNames(className, classes.table)}>
|
|
22
|
+
{children}
|
|
23
|
+
</table>
|
|
24
|
+
);
|
|
25
|
+
};
|
|
26
|
+
|
|
27
|
+
export default Table;
|