@unitedstatespowersquadrons/components 1.3.11 → 1.3.12

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.
@@ -17,7 +17,7 @@ jobs:
17
17
  runs-on: ubuntu-latest
18
18
  steps:
19
19
  - name: Checkout Code
20
- uses: actions/checkout@v4
20
+ uses: actions/checkout@v6
21
21
  - name: Install Packages
22
22
  run: yarn
23
23
  - name: Run Eslint
@@ -26,7 +26,7 @@ jobs:
26
26
  runs-on: ubuntu-latest
27
27
  steps:
28
28
  - name: Checkout Code
29
- uses: actions/checkout@v4
29
+ uses: actions/checkout@v6
30
30
  - name: Install Packages
31
31
  run: yarn
32
32
  - name: Run TSC
@@ -35,7 +35,7 @@ jobs:
35
35
  runs-on: ubuntu-latest
36
36
  steps:
37
37
  - name: Checkout Code
38
- uses: actions/checkout@v4
38
+ uses: actions/checkout@v6
39
39
  - name: Install Packages
40
40
  run: yarn
41
41
  - name: Run Tests
@@ -0,0 +1,43 @@
1
+ import React from "react";
2
+ import { createUseStyles } from "react-jss";
3
+ import classNames from "classnames";
4
+ import { OnMouseMoveHandler } from "../types";
5
+ import Styles from "../Styles";
6
+
7
+ const useStyles = createUseStyles({
8
+ adminMenuItem: {
9
+ "&:not(:last-child)": {
10
+ padding: "0.5em 0.1em",
11
+ },
12
+ ...Styles.flexRow,
13
+ borderRadius: "0.5em",
14
+ justifyContent: "left",
15
+ width: "100%",
16
+ },
17
+ });
18
+
19
+ interface Props {
20
+ children: React.ReactNode;
21
+ className?: string;
22
+ onClick?: OnMouseMoveHandler;
23
+ onMouseEnter?: OnMouseMoveHandler;
24
+ onMouseLeave?: OnMouseMoveHandler;
25
+ }
26
+
27
+ const AdminItem = (props: Props) => {
28
+ const { children, className, onClick, onMouseEnter, onMouseLeave } = props;
29
+ const classes = useStyles();
30
+
31
+ return(
32
+ <div
33
+ className={classNames(classes.adminMenuItem, className)}
34
+ onClick={onClick}
35
+ onMouseEnter={onMouseEnter}
36
+ onMouseLeave={onMouseLeave}
37
+ >
38
+ {children}
39
+ </div>
40
+ );
41
+ };
42
+
43
+ export default AdminItem;
@@ -0,0 +1,141 @@
1
+ import React, { useRef } from "react";
2
+ import { createUseStyles } from "react-jss";
3
+ import classNames from "classnames";
4
+ import AdminItem from "./AdminItem";
5
+ import { AdminAction, Impersonation as ImpersonationType } from "./types";
6
+ import ActionButton from "../ActionButton";
7
+ import FontAwesomeIcon from "../FontAwesomeIcon";
8
+ import IconButton from "../IconButton";
9
+ import Styles from "../Styles";
10
+ import { OnClickHandler, OnClickOrKeyboardHandler } from "../types";
11
+
12
+ const useStyles = createUseStyles({
13
+ button: { paddingRight: "0.75em !important" },
14
+ center: {
15
+ "& svg": { margin: "0 auto" },
16
+ justifyContent: "center",
17
+ },
18
+ gray: { ...Styles.gray },
19
+ impersonation: {
20
+ animation: "300ms ease-in",
21
+ display: "flex",
22
+ flexDirection: "column",
23
+ gap: "0.25em",
24
+ paddingBottom: "0.5em",
25
+ },
26
+ inactive: {
27
+ "& > div": {
28
+ display: "none",
29
+ opacity: "0",
30
+ transition: "opacity 0.5s ease-in-out",
31
+ },
32
+ "& > svg": { display: "block" },
33
+ "&:hover > div": {
34
+ display: "flex",
35
+ opacity: "1",
36
+ },
37
+ "&:hover > svg": { display: "none" },
38
+ },
39
+ left: { textAlign: "left" },
40
+ viewAsIcon: {
41
+ ...Styles.purple,
42
+ fontSize: "2em",
43
+ },
44
+ viewingAsIcon: {
45
+ "&:hover": { ...Styles.hover.red },
46
+ ...Styles.red,
47
+ fontSize: "1.5em",
48
+ },
49
+ });
50
+
51
+ interface Hooks {
52
+ useImpersonation: () => { impersonation?: ImpersonationType | null };
53
+ useDispatch: () => (action: AdminAction) => void;
54
+ }
55
+
56
+ const buildImpersonation = (hooks: Hooks) => {
57
+ const { useImpersonation, useDispatch } = hooks;
58
+
59
+ const Impersonation = () => {
60
+ const { impersonation } = useImpersonation();
61
+ const dispatch = useDispatch();
62
+ const classes = useStyles();
63
+
64
+ const inputRef = useRef<HTMLInputElement>(null);
65
+
66
+ const handleImpersonate: OnClickOrKeyboardHandler = event => {
67
+ if ("key" in event && event.key !== "Enter") { return; }
68
+
69
+ if (!inputRef.current) return;
70
+
71
+ const certificate = inputRef.current.value;
72
+
73
+ dispatch({ certificate, type: "impersonate" });
74
+ };
75
+
76
+ const handleCancel: OnClickHandler = () => {
77
+ dispatch({ type: "cancelImpersonation" });
78
+ };
79
+
80
+ if (impersonation) {
81
+ const { certificate, name } = impersonation;
82
+
83
+ return(
84
+ <AdminItem className={classes.impersonation} key="impersonating">
85
+ <div className="bold">Viewing As:</div>
86
+ <IconButton
87
+ className={classes.viewingAsIcon}
88
+ icon="user-group"
89
+ mode="duotone"
90
+ onClick={handleCancel}
91
+ title="Cancel Impersonation"
92
+ />
93
+ <div>{name}</div>
94
+ <div className={classes.gray}>{certificate}</div>
95
+ </AdminItem>
96
+ );
97
+ }
98
+
99
+ const collapsed = (
100
+ <FontAwesomeIcon
101
+ css={classes.viewAsIcon}
102
+ icon="user-group"
103
+ mode="duotone"
104
+ />
105
+ );
106
+
107
+ const expanded = (
108
+ <div className={classNames(classes.impersonation, classes.center)}>
109
+ <div className="bold">View As:</div>
110
+ <FontAwesomeIcon css={classes.viewAsIcon} icon="user-group" mode="duotone" />
111
+ <input
112
+ maxLength={7}
113
+ onKeyDown={handleImpersonate}
114
+ placeholder="Certificate"
115
+ ref={inputRef}
116
+ type="text"
117
+ />
118
+ <ActionButton
119
+ className={classes.button}
120
+ color="purple"
121
+ icon="shuffle"
122
+ mode="duotone"
123
+ onClick={handleImpersonate}
124
+ size="small"
125
+ text="Impersonate"
126
+ />
127
+ </div>
128
+ );
129
+
130
+ return(
131
+ <AdminItem className={classes.inactive}>
132
+ {collapsed}
133
+ {expanded}
134
+ </AdminItem>
135
+ );
136
+ };
137
+
138
+ return Impersonation;
139
+ };
140
+
141
+ export default buildImpersonation;
@@ -0,0 +1,67 @@
1
+ import { AdminAction } from "./types";
2
+ import { AppActionType, DispatchFunc, DispatchHandler, GenericAppAction, wrapDispatch } from "../Reducer";
3
+
4
+ // Callers' AppAction union must include AdminAction members. We don't encode
5
+ // that in the generic (TS can't express "A is a supertype of AdminAction"
6
+ // without distribution headaches), so we narrow via type predicates at
7
+ // runtime instead of casting.
8
+
9
+ const isImpersonate = (action: GenericAppAction): action is Extract<AdminAction, { type: "impersonate" }> =>
10
+ action.type === "impersonate";
11
+
12
+ export const wrapImpersonate = <A extends GenericAppAction>(dispatch: DispatchFunc<A>): DispatchFunc<A> => {
13
+ const actionType = "impersonate" as AppActionType<A>;
14
+
15
+ const handler: DispatchHandler<A> = action => {
16
+ if (!isImpersonate(action)) return;
17
+
18
+ const { certificate } = action;
19
+
20
+ return { bodyJson: { certificate }, href: "/admin/impersonate" };
21
+ };
22
+
23
+ return wrapDispatch(dispatch, [actionType], handler);
24
+ };
25
+
26
+ export const wrapCancelImpersonation = <A extends GenericAppAction>(dispatch: DispatchFunc<A>): DispatchFunc<A> => {
27
+ const actionType = "cancelImpersonation" as AppActionType<A>;
28
+
29
+ const handler: DispatchHandler<A> = action => {
30
+ if (action.type !== actionType) return;
31
+
32
+ return { href: "/admin/cancel", method: "DELETE" };
33
+ };
34
+
35
+ return wrapDispatch(dispatch, [actionType], handler);
36
+ };
37
+
38
+ export const wrapExpand = <A extends GenericAppAction>(dispatch: DispatchFunc<A>): DispatchFunc<A> => {
39
+ const actionType = "expand" as AppActionType<A>;
40
+
41
+ const handler: DispatchHandler<A> = action => {
42
+ if (action.type !== actionType) return;
43
+
44
+ return { href: "/admin/expand" };
45
+ };
46
+
47
+ return wrapDispatch(dispatch, [actionType], handler);
48
+ };
49
+
50
+ export const wrapCollapse = <A extends GenericAppAction>(dispatch: DispatchFunc<A>): DispatchFunc<A> => {
51
+ const actionType = "collapse" as AppActionType<A>;
52
+
53
+ const handler: DispatchHandler<A> = action => {
54
+ if (action.type !== actionType) return;
55
+
56
+ return { href: "/admin/collapse" };
57
+ };
58
+
59
+ return wrapDispatch(dispatch, [actionType], handler);
60
+ };
61
+
62
+ export const adminMenuHandlers = {
63
+ wrapCancelImpersonation,
64
+ wrapCollapse,
65
+ wrapExpand,
66
+ wrapImpersonate,
67
+ };
@@ -0,0 +1,4 @@
1
+ export { default as AdminItem } from "./AdminItem";
2
+ export { default as buildImpersonation } from "./Impersonation";
3
+ export * from "./handlers";
4
+ export type * from "./types";
package/Admin/types.ts ADDED
@@ -0,0 +1,10 @@
1
+ export interface Impersonation {
2
+ certificate: string;
3
+ name: string;
4
+ }
5
+
6
+ export type AdminAction =
7
+ | { type: "expand" }
8
+ | { type: "collapse" }
9
+ | { type: "impersonate"; certificate: string }
10
+ | { type: "cancelImpersonation" };
package/index.tsx CHANGED
@@ -24,6 +24,8 @@ export * from "./Toasts";
24
24
  export { default as Colors } from "./Colors";
25
25
  export { default as Styles } from "./Styles";
26
26
 
27
+ export * from "./Admin";
28
+
27
29
  export type * from "./types";
28
30
  export type * from "./Reducer";
29
31
  export type * from "./Colors";
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@unitedstatespowersquadrons/components",
3
- "version": "1.3.11",
3
+ "version": "1.3.12",
4
4
  "description": "USPS shared React components library",
5
5
  "main": "index.tsx",
6
6
  "scripts": {