@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.
@@ -0,0 +1,46 @@
1
+ import Colors from "../Colors";
2
+
3
+ const thead = {
4
+ "& > tr" : {
5
+ "& > td, & > th": {
6
+ backgroundColor: Colors.shades._b,
7
+ height: "2em",
8
+ },
9
+ },
10
+ };
11
+
12
+ const tbody = {
13
+ "& > tr" : {
14
+ "&:nth-child(even)": {
15
+ "& > td, & > th": {
16
+ backgroundColor: Colors.shades._e,
17
+ height: "1.75em",
18
+ },
19
+ },
20
+ "&:nth-child(odd)": {
21
+ "& > td, & > th": {
22
+ backgroundColor: Colors.shades._d,
23
+ height: "1.75em",
24
+ },
25
+ },
26
+ },
27
+ };
28
+
29
+ const tfoot = {
30
+ "& > tr" : {
31
+ "& > td": {
32
+ backgroundColor: Colors.shades._c,
33
+ height: "1.75em",
34
+ },
35
+ },
36
+ };
37
+
38
+ const table = {
39
+ "& > tbody": tbody,
40
+ "& > tfoot": tfoot,
41
+ "& > thead": thead,
42
+ };
43
+
44
+ const TableStyles = { table, tbody, tfoot, thead };
45
+
46
+ export default TableStyles;
@@ -0,0 +1,9 @@
1
+ import Table from "./Table";
2
+ import Head from "./Head";
3
+ import Body from "./Body";
4
+
5
+ export { default as StripedTable } from "./Table";
6
+ export { default as StripedHead } from "./Head";
7
+ export { default as StripedBody } from "./Body";
8
+
9
+ export default { Body, Head, Table };
package/Styles.tsx ADDED
@@ -0,0 +1,67 @@
1
+ import Colors, { ButtonColorName } from "./Colors";
2
+
3
+ const colorStyles = {
4
+ black: { color: Colors.black },
5
+ blue: { color: Colors.blue },
6
+ darkGray: { color: Colors.darkGray },
7
+ gray: { color: Colors.gray },
8
+ green: { color: Colors.green },
9
+ orange: { color: Colors.orange },
10
+ purple: { color: Colors.purple },
11
+ red: { color: Colors.red },
12
+ };
13
+
14
+ const hoverColorStyles = {
15
+ black: { color: Colors.darkGray },
16
+ blue: { color: Colors.hover.blue },
17
+ darkGray: { color: Colors.black },
18
+ gray: { color: Colors.darkGray },
19
+ green: { color: Colors.hover.green },
20
+ orange: { color: Colors.hover.orange },
21
+ purple: { color: Colors.hover.purple },
22
+ red: { color: Colors.hover.red },
23
+ };
24
+
25
+ const colorizeButton = (color: ButtonColorName) => {
26
+ return({
27
+ "&:hover": {
28
+ backgroundColor: `${Colors.button.background.hover[color]} !important`,
29
+ fontWeight: "normal",
30
+ },
31
+ backgroundColor: `${Colors.button.background.normal[color]} !important`,
32
+ color: `${Colors.black} !important`,
33
+ });
34
+ };
35
+
36
+ const Styles = {
37
+ bigText: { fontSize: "1.5em" },
38
+ button: {
39
+ "&:hover": {
40
+ backgroundColor: Colors.button.background.hover.gray,
41
+ fontWeight: "normal",
42
+ },
43
+ alignItems: "center",
44
+ backgroundColor: Colors.button.background.normal.gray,
45
+ border: `thin solid ${Colors.button.border}`,
46
+ borderRadius: "0.25em",
47
+ boxShadow: `2px 2px 2px ${Colors.button.shadow}`,
48
+ cursor: "pointer",
49
+ display: "flex !important",
50
+ margin: "0.5em auto 0 auto",
51
+ padding: "0 0.7em 0 0.3em",
52
+ textDecoration: "none",
53
+ width: "fit-content",
54
+ },
55
+ flexRow: {
56
+ alignItems: "center",
57
+ display: "flex",
58
+ flexDirection: "row",
59
+ gap: "0.5em",
60
+ justifyContent: "center",
61
+ },
62
+ ...colorStyles,
63
+ colorizeButton,
64
+ hover: { ...hoverColorStyles },
65
+ };
66
+
67
+ export default Styles;
@@ -0,0 +1,110 @@
1
+ # Toasts Component
2
+
3
+ The shared toasts component allows for rendering consistent toasts across any
4
+ root components, while supporting app state and reducer dispatching.
5
+
6
+ ```tsx
7
+ // NewComponent.tsx
8
+ import { useAppState, useAppDispatch } from "./reducer";
9
+ import { createDismissToast, Toasts } from "../shared";
10
+
11
+ const NewComponent = () => {
12
+ const { toasts } = useAppState();
13
+ const dispatch = useAppDispatch();
14
+ const dismissToast = createDismissToast(dispatch);
15
+
16
+ return (
17
+ <Toasts dismiss={dismissToast} toasts={toasts} />
18
+ );
19
+ };
20
+
21
+ export default NewComponent;
22
+ ```
23
+
24
+ ```tsx
25
+ // reducer.tsx
26
+ import { Toast, ToastAppAction, ToastProps } from "../shared";
27
+ import { addToast, handleToasts } from "./reducerHelpers";
28
+
29
+ export type AppAction =
30
+ // ...
31
+ | ToastAppAction
32
+ | { type: "saveComplete"; toasts: Toast[] }
33
+ // ...
34
+ ;
35
+
36
+ function reducer(draft: Draft<AppState>, action: AppAction) {
37
+ // ..
38
+ const toast = "toastId" in action ? draft.toasts.find(t => t.id === action.toastId) : null;
39
+ // ...
40
+ case "addToast":
41
+ addToast(draft, action);
42
+
43
+ break;
44
+
45
+ case "dismissToast":
46
+ if (toast) toast.dismissed = true;
47
+
48
+ break;
49
+
50
+ case "removeToast":
51
+ // draft.toasts.splice was removing toasts too quickly
52
+ const beforeToasts: ToastProps[] = [];
53
+ const afterToasts: ToastProps[] = [];
54
+ let beforeToast = true;
55
+ draft.toasts.forEach(t => {
56
+ if (t === toast) {
57
+ beforeToast = false;
58
+ } else if (beforeToast) {
59
+ beforeToasts.push(t);
60
+ } else {
61
+ afterToasts.push(t);
62
+ }
63
+
64
+ draft.toasts = beforeToasts.concat(afterToasts);
65
+ });
66
+
67
+ break;
68
+
69
+ case "saveComplete":
70
+ handleToasts(draft, action.toasts);
71
+
72
+ break;
73
+ // ...
74
+ };
75
+ ```
76
+
77
+ ```tsx
78
+ // reducerHelpers.tsx
79
+ import { Draft } from "immer";
80
+ import { AppState } from "./types";
81
+ import { Toast, ToastProps } from "../shared";
82
+
83
+ export const handleToasts = (draft: Draft<AppState>, toasts: Toast[]) => {
84
+ if (toasts.length > 0) {
85
+ toasts.forEach(toast => addToast(draft, toast));
86
+ }
87
+ };
88
+
89
+ export const addToast = (draft: Draft<AppState>, toastOptions: Proto.Shared.Toast) => {
90
+ const { body, status, timeout, title } = toastOptions;
91
+
92
+ const id = draft.toastId = draft.toastId ? draft.toastId + 1 : 1;
93
+
94
+ const toast = { body, id, status, timeout, title } as ToastProps;
95
+
96
+ if (!draft.toasts) draft.toasts = [];
97
+
98
+ // Do not store multiple identical toasts
99
+ if (draft.toasts.some(t => {
100
+ return (
101
+ toast.body === t.body &&
102
+ toast.status === t.status &&
103
+ toast.title === t.title
104
+ );
105
+ })) return;
106
+
107
+ draft.toasts.push(toast);
108
+ };
109
+
110
+ ```
@@ -0,0 +1,162 @@
1
+ import React from "react";
2
+ import classNames from "classnames";
3
+ import { createUseStyles } from "react-jss";
4
+ import FontAwesomeIcon from "../FontAwesomeIcon";
5
+ import { DismissFunc, ToastProps, ToastRemoveAppAction, Toast_Status } from "./types";
6
+ import { DispatchFunc } from "../types";
7
+ import createDismissToast from "./createDismissToast";
8
+
9
+ interface BaseProps {
10
+ toast: ToastProps;
11
+ }
12
+
13
+ interface DismissProps extends BaseProps {
14
+ dismiss?: DismissFunc;
15
+ }
16
+
17
+ interface DispatchProps extends BaseProps {
18
+ dispatch?: DispatchFunc<ToastRemoveAppAction>;
19
+ }
20
+
21
+ type Props = DismissProps | DispatchProps;
22
+
23
+ const useStyles = createUseStyles({
24
+ dismissed: { opacity: "0" },
25
+ error: {
26
+ borderColor: "#CC0000",
27
+ color: "#CC0000",
28
+ },
29
+ italic: { fontStyle: "italic" },
30
+ notice: {
31
+ borderColor: "#000099",
32
+ color: "#000099",
33
+ },
34
+ success: {
35
+ borderColor: "#009900",
36
+ color: "#009900",
37
+ },
38
+ toast: {
39
+ animation: "fadeIn 1s",
40
+ backgroundColor: "#EEEEEE",
41
+ border: "thin solid white",
42
+ borderRadius: "0.5em",
43
+ boxShadow: "0px 4px 8px rgba(0, 0, 0, 0.1), 0px 0px 2px rgba(0, 0, 0, 0.4), 0px 0px 1px rgba(0, 0, 0, 0.3)",
44
+ padding: "1em",
45
+ position: "relative",
46
+ transition: "opacity 2000ms linear",
47
+ width: "30em",
48
+ },
49
+ toastBody: {
50
+ "& > div": { marginTop: "0.75em" },
51
+ fontSize: "0.9em",
52
+ lineHeight: "1.3em",
53
+ marginLeft: "2.75em",
54
+ textAlign: "left",
55
+ },
56
+ toastDismiss: {
57
+ color: "#333333",
58
+ cursor: "pointer",
59
+ fontSize: "1.5em",
60
+ position: "absolute",
61
+ right: "10px",
62
+ top: "10px",
63
+ },
64
+ toastDismissIcon: {
65
+ fontSize: "1em",
66
+ width: "24px",
67
+ },
68
+ toastTitle: {
69
+ "& > span": {
70
+ fontSize: "1em",
71
+ fontWeight: "bold",
72
+ },
73
+ marginBottom: "1em",
74
+ textAlign: "left",
75
+ },
76
+ visible: { opacity: "1" },
77
+ });
78
+
79
+ const Toast = (props: Props) => {
80
+ const classes = useStyles();
81
+
82
+ const { toast } = props;
83
+ const { body, dismissed, id, status, timeout, title } = toast;
84
+
85
+ const dismissFunc = (): DismissFunc => {
86
+ let dismiss;
87
+
88
+ if ("dismiss" in props) {
89
+ dismiss = props.dismiss;
90
+ } else if ("dispatch" in props) {
91
+ const { dispatch } = props;
92
+ dismiss = createDismissToast(dispatch);
93
+ } else {
94
+ throw new Error("Invalid Toast dismiss configuration");
95
+ }
96
+
97
+ return dismiss;
98
+ };
99
+
100
+ const handleDismiss = () => dismissFunc()(id);
101
+
102
+ if (timeout > 0) setTimeout(handleDismiss, 2000 + timeout);
103
+
104
+ const renderBodyLines = (bodyLines: string[]) => {
105
+ return(bodyLines.map((line, index) => {
106
+ if (/^https?:\/\//.exec(line)) {
107
+ return(
108
+ <div key={`line-${index}`}>
109
+ <a href={line}>{line}</a>
110
+ </div>
111
+ );
112
+ } else {
113
+ return(<div key={`line-${index}`}>{line}</div>);
114
+ }
115
+ }));
116
+ };
117
+
118
+ const renderBody = () => {
119
+ const lines = body.split("\n");
120
+ if (lines[0]?.match(/^{\w+}$/)) {
121
+ const [tag, ...rest] = lines;
122
+ const tagText = tag.substring(1, tag.length - 1); // e.g. {Development}
123
+
124
+ return (
125
+ <>
126
+ <div className={classes.italic}>{tagText}</div>
127
+ {renderBodyLines(rest)}
128
+ </>
129
+ );
130
+ } else {
131
+ return renderBodyLines(lines);
132
+ }
133
+ };
134
+
135
+ const statusDetails = (): [string, string] => {
136
+ switch (status) {
137
+ case Toast_Status.SUCCESS: return [classes.success, "check"];
138
+ case Toast_Status.NOTICE: return [classes.notice, "circle-info"];
139
+ case Toast_Status.ERROR: return [classes.error, "ban"];
140
+ default: return status satisfies never;
141
+ }
142
+ };
143
+
144
+ const [statusClassName, icon] = statusDetails();
145
+
146
+ const opacityClass = dismissed ? classes.dismissed : classes.visible;
147
+
148
+ return (
149
+ <div className={classNames(classes.toast, statusClassName, opacityClass)} key={`toast-${id}`}>
150
+ <div className={classes.toastTitle}>
151
+ <FontAwesomeIcon icon={icon} mode="duotone" fa="fw" />
152
+ <span>{title}</span>
153
+ </div>
154
+ <div className={classes.toastBody}>{renderBody()}</div>
155
+ <a className={classes.toastDismiss} title="Dismiss this message" onClick={handleDismiss}>
156
+ <FontAwesomeIcon icon="times" mode="duotone" fa="fw" css={classes.toastDismissIcon} />
157
+ </a>
158
+ </div>
159
+ );
160
+ };
161
+
162
+ export default Toast;
@@ -0,0 +1,36 @@
1
+ import React from "react";
2
+ import classNames from "classnames";
3
+ import { createUseStyles } from "react-jss";
4
+ import Toast from "./Toast";
5
+ import { ToastProps } from "./types";
6
+
7
+ interface Props {
8
+ dismiss: (id: number) => void;
9
+ toasts?: ToastProps[];
10
+ }
11
+
12
+ const useStyles = createUseStyles({
13
+ toastsContainer: {
14
+ display: "flex",
15
+ flexDirection: "column",
16
+ gap: "1.5em",
17
+ justifyContent: "space-between",
18
+ position: "fixed",
19
+ right: "1.25em",
20
+ top: "1.25em",
21
+ zIndex: 9999,
22
+ },
23
+ });
24
+
25
+ const Toasts = (props: Props) => {
26
+ const { dismiss, toasts } = props;
27
+ const classes = useStyles();
28
+
29
+ return (
30
+ <div className={classNames("toastsContainer", classes.toastsContainer)}>
31
+ {toasts?.map(toast => <Toast dismiss={dismiss} toast={toast} key={toast.id} />)}
32
+ </div>
33
+ );
34
+ };
35
+
36
+ export default Toasts;
@@ -0,0 +1,16 @@
1
+ import { ToastRemoveAppAction } from "./types";
2
+ import { DispatchFunc } from "../types";
3
+
4
+ const createDismissToast = (dispatch: DispatchFunc<ToastRemoveAppAction>) => {
5
+ const dismissToast = (toastId: number) => {
6
+ dispatch({ toastId, type: "dismissToast" });
7
+
8
+ setTimeout(() => {
9
+ dispatch({ toastId, type: "removeToast" });
10
+ }, 2000);
11
+ };
12
+
13
+ return dismissToast;
14
+ };
15
+
16
+ export default createDismissToast;
@@ -0,0 +1,5 @@
1
+ export { default as Toasts } from "./Toasts";
2
+ export { default as createDismissToast } from "./createDismissToast";
3
+
4
+ export { Toast_Status } from "./types";
5
+ export type { Toast, ToastAppAction, ToastProps } from "./types";
@@ -0,0 +1,28 @@
1
+ export interface ToastProps extends Toast {
2
+ dismissed?: boolean;
3
+ id: number;
4
+ }
5
+
6
+ export type ToastRemoveAppAction =
7
+ | { type: "dismissToast"; toastId: number }
8
+ | { type: "removeToast"; toastId: number };
9
+
10
+ export type ToastAppAction =
11
+ | { type: "addToast" } & Toast
12
+ | ToastRemoveAppAction;
13
+
14
+ export interface Toast {
15
+ status: Toast_Status;
16
+ title: string;
17
+ body: string;
18
+ timeout: number;
19
+ toastId?: number | undefined;
20
+ }
21
+
22
+ export enum Toast_Status {
23
+ SUCCESS = "SUCCESS",
24
+ NOTICE = "NOTICE",
25
+ ERROR = "ERROR",
26
+ }
27
+
28
+ export type DismissFunc = (id: number) => void;
@@ -0,0 +1,212 @@
1
+ import globals from "globals";
2
+ import eslint from "@eslint/js";
3
+ import stylisticJs from "@stylistic/eslint-plugin";
4
+ import react from "eslint-plugin-react";
5
+ import reactHooks from "eslint-plugin-react-hooks";
6
+ import tseslint from "typescript-eslint";
7
+
8
+ export default tseslint.config(
9
+ eslint.configs.recommended,
10
+ tseslint.configs.recommendedTypeChecked,
11
+ {
12
+ languageOptions: {
13
+ globals: globals.browser,
14
+ parserOptions: {
15
+ projectService: true,
16
+ tsconfigRootDir: import.meta.dirname,
17
+ },
18
+ },
19
+ },
20
+ {
21
+ rules: {
22
+ "indent": ["error", 2],
23
+ "linebreak-style": ["error", "unix"],
24
+ "max-len": ["error", 120],
25
+ "no-case-declarations": "error",
26
+ "no-var": "error",
27
+ "prefer-const": "error",
28
+ "quotes": ["error", "double"],
29
+ "semi": ["error", "always"],
30
+ "sort-keys": ["error", "asc", { caseSensitive: true, minKeys: 2, natural: false }],
31
+ "arrow-parens": ["error", "as-needed"],
32
+ "comma-dangle": [
33
+ "error",
34
+ {
35
+ "arrays": "always-multiline",
36
+ "exports": "always-multiline",
37
+ "functions": "never",
38
+ "imports": "always-multiline",
39
+ "objects": "always-multiline"
40
+ }
41
+ ],
42
+ "constructor-super": "error",
43
+ "curly": ["error", "multi-line"],
44
+ "dot-notation": "off", // This is superseded by @typescript-eslint/dot-notation
45
+ "eqeqeq": ["error", "smart"],
46
+ "guard-for-in": "error",
47
+ "id-blacklist": "error",
48
+ "id-match": "error",
49
+ "max-classes-per-file": ["error", 1],
50
+ "max-len": [
51
+ "error",
52
+ {
53
+ "code": 120,
54
+ }
55
+ ],
56
+ "new-parens": "error",
57
+ "no-bitwise": "error",
58
+ "no-caller": "error",
59
+ "no-cond-assign": "error",
60
+ "no-console": "error",
61
+ "no-debugger": "error",
62
+ "no-eval": "error",
63
+ "no-fallthrough": "error",
64
+ "no-new-wrappers": "error",
65
+ "no-shadow": "off", // This is superseded by @typescript-eslint/no-shadow
66
+ "no-throw-literal": "error",
67
+ "no-trailing-spaces": "error",
68
+ "no-undef-init": "error",
69
+ "no-unsafe-finally": "error",
70
+ "no-unused-expressions": [
71
+ "error",
72
+ {
73
+ "allowShortCircuit": true
74
+ }
75
+ ],
76
+ "no-unused-labels": "error",
77
+ "no-var": "error",
78
+ "object-curly-spacing": ["error", "always"],
79
+ "object-shorthand": "error",
80
+ "one-var": ["error", "never"],
81
+ "prefer-const": "error",
82
+ "prefer-rest-params": "error",
83
+ "prefer-spread": "error",
84
+ "quote-props": ["error", "as-needed"],
85
+ "quotes": ["error", "double", { "allowTemplateLiterals": true, "avoidEscape": true }],
86
+ "radix": "error",
87
+ "sort-keys": "error",
88
+ "spaced-comment": "error",
89
+ "use-isnan": "error",
90
+
91
+ "@typescript-eslint/adjacent-overload-signatures": "error",
92
+ "@typescript-eslint/array-type": "error",
93
+ "@typescript-eslint/await-thenable": "error",
94
+ "@typescript-eslint/ban-ts-comment": "error",
95
+ "@typescript-eslint/dot-notation": "error",
96
+ "@typescript-eslint/explicit-member-accessibility": "error",
97
+ "@typescript-eslint/no-array-constructor": "error",
98
+ "@typescript-eslint/no-extra-non-null-assertion": "error",
99
+ "@typescript-eslint/no-floating-promises": "error",
100
+ "@typescript-eslint/no-for-in-array": "error",
101
+ "@typescript-eslint/no-implied-eval": "error",
102
+ "@typescript-eslint/no-misused-new": "error",
103
+ "@typescript-eslint/no-misused-promises": "error",
104
+ "@typescript-eslint/no-namespace": "error",
105
+ "@typescript-eslint/no-non-null-asserted-optional-chain": "error",
106
+ "@typescript-eslint/no-shadow": [
107
+ "error",
108
+ {
109
+ "hoist": "all"
110
+ }
111
+ ],
112
+ "@typescript-eslint/no-this-alias": "error",
113
+ "@typescript-eslint/no-unnecessary-type-assertion": "error",
114
+ "@typescript-eslint/no-unsafe-call": "error",
115
+ "@typescript-eslint/no-unsafe-return": "error",
116
+ "@typescript-eslint/no-var-requires": "error",
117
+ "@typescript-eslint/prefer-as-const": "error",
118
+ "@typescript-eslint/prefer-for-of": "error",
119
+ "@typescript-eslint/prefer-function-type": "error",
120
+ "@typescript-eslint/prefer-includes": "error",
121
+ "@typescript-eslint/prefer-namespace-keyword": "error",
122
+ "@typescript-eslint/prefer-regexp-exec": "error",
123
+ "@typescript-eslint/require-await": "error",
124
+ "@typescript-eslint/triple-slash-reference": "error",
125
+ "@typescript-eslint/unified-signatures": "error",
126
+ "@typescript-eslint/no-restricted-types": "error",
127
+ "@typescript-eslint/no-empty-object-type": "error",
128
+ "@typescript-eslint/no-wrapper-object-types": "error",
129
+
130
+ "stylistic/member-delimiter-style": [
131
+ "error",
132
+ {
133
+ "multiline": {
134
+ "delimiter": "semi",
135
+ "requireLast": true
136
+ },
137
+ "singleline": {
138
+ "delimiter": "semi",
139
+ "requireLast": false
140
+ }
141
+ }
142
+ ],
143
+ "stylistic/no-extra-semi": "error",
144
+ "stylistic/semi": ["error", "always"],
145
+ "stylistic/no-multiple-empty-lines": "error",
146
+
147
+ "react/display-name": "error",
148
+ "react/jsx-boolean-value": ["error", "always"],
149
+ "react/jsx-closing-bracket-location": "error",
150
+ "react/jsx-equals-spacing": ["error", "never"],
151
+ "react/jsx-key": ["error", { "checkFragmentShorthand": false }],
152
+ "react/jsx-no-comment-textnodes": "error",
153
+ "react/jsx-no-duplicate-props": "error",
154
+ "react/jsx-no-target-blank": "error",
155
+ "react/jsx-no-undef": "error",
156
+ "react/jsx-uses-react": "error",
157
+ "react/jsx-uses-vars": "error",
158
+ "react/jsx-wrap-multilines": "error",
159
+ "react/no-children-prop": "error",
160
+ "react/no-danger-with-children": "error",
161
+ "react/no-deprecated": "error",
162
+ "react/no-direct-mutation-state": "error",
163
+ "react/no-find-dom-node": "error",
164
+ "react/no-is-mounted": "error",
165
+ "react/no-render-return-value": "error",
166
+ "react/no-string-refs": ["error", { "noTemplateLiterals": true }],
167
+ "react/no-unescaped-entities": "error",
168
+ "react/no-unknown-property": "error",
169
+ "react/no-unsafe": 0,
170
+ "react/prop-types": "off", // Use typescript to validate types.
171
+ "react/react-in-jsx-scope": "error",
172
+ "react/require-render-return": "error",
173
+ "react/self-closing-comp": [
174
+ "error",
175
+ {
176
+ "component": true,
177
+ "html": true
178
+ }
179
+ ],
180
+
181
+ "react-hooks/exhaustive-deps": "error",
182
+ "react-hooks/rules-of-hooks": "error",
183
+ },
184
+ settings: {
185
+ react: {
186
+ createClass: "createReactClass",
187
+ pragma: "React",
188
+ version: "detect",
189
+ },
190
+ },
191
+ },
192
+ {
193
+ ignores: [
194
+ "tmp/*",
195
+ "coverage/*",
196
+ "public/*",
197
+ "vendor/*",
198
+ "app/assets/*",
199
+ "app/javascript/controllers/*",
200
+ "app/javascript/application.js",
201
+ "app/javascript/mount.tsx",
202
+ "eslint.config.mjs",
203
+ ],
204
+ },
205
+ {
206
+ plugins: {
207
+ "stylistic": stylisticJs,
208
+ "react": react,
209
+ "react-hooks": reactHooks,
210
+ },
211
+ },
212
+ );