dce-reactkit 3.0.40 → 3.0.43

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,267 @@
1
+ /**
2
+ * Copiable text box
3
+ * @author Gabe Abrams
4
+ */
5
+
6
+ // Import React
7
+ import React, { useReducer } from 'react';
8
+
9
+ // Import other reactkit functions
10
+ import { alert, waitMs } from '..';
11
+
12
+ // Import FontAwesome
13
+ import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
14
+ import { faClipboard } from '@fortawesome/free-solid-svg-icons';
15
+
16
+ /*------------------------------------------------------------------------*/
17
+ /* Types */
18
+ /*------------------------------------------------------------------------*/
19
+
20
+ // Props definition
21
+ type Props = {
22
+ // Unique name of the item to copy (no spaces, compatible with css classes)
23
+ name: string,
24
+ // The text to copy
25
+ text: string,
26
+ // Human-readable label of the copy field
27
+ label: string,
28
+ // FontAwesome icon to place before the label
29
+ labelIcon?: any,
30
+ // If defined, the label will have a minimum width
31
+ minLabelWidthRem: number,
32
+ // If true, the box will be a textarea to support larger, multiline text
33
+ multiline?: boolean,
34
+ // Number of lines to show in multiline view (only relevant if multiline)
35
+ numVisibleLines?: number,
36
+ };
37
+
38
+ /*------------------------------------------------------------------------*/
39
+ /* State */
40
+ /*------------------------------------------------------------------------*/
41
+
42
+ /* -------- State Definition -------- */
43
+
44
+ type State = {
45
+ // True if text was recently copied
46
+ recentlyCopied: boolean,
47
+ };
48
+
49
+ /* ------------- Actions ------------ */
50
+
51
+ // Types of actions
52
+ enum ActionType {
53
+ // Indicate that the text was recently copied
54
+ IndicateRecentlyCopied = 'indicate-recently-copied',
55
+ // Clear the status
56
+ ClearRecentlyCopiedStatus = 'clear-recently-copied-status',
57
+ }
58
+
59
+ // Action definitions
60
+ type Action = {
61
+ // Action type
62
+ type: (
63
+ | ActionType.IndicateRecentlyCopied
64
+ | ActionType.ClearRecentlyCopiedStatus
65
+ ),
66
+ };
67
+
68
+ /**
69
+ * Reducer that executes actions
70
+ * @author Gabe Abrams
71
+ * @param state current state
72
+ * @param action action to execute
73
+ */
74
+ const reducer = (state: State, action: Action): State => {
75
+ switch (action.type) {
76
+ case ActionType.IndicateRecentlyCopied: {
77
+ return {
78
+ recentlyCopied: true,
79
+ };
80
+ }
81
+ case ActionType.ClearRecentlyCopiedStatus: {
82
+ return {
83
+ recentlyCopied: false,
84
+ };
85
+ }
86
+ default: {
87
+ return state;
88
+ }
89
+ }
90
+ };
91
+
92
+ /*------------------------------------------------------------------------*/
93
+ /* Component */
94
+ /*------------------------------------------------------------------------*/
95
+
96
+ const CopiableBox: React.FC<Props> = (props) => {
97
+ /*------------------------------------------------------------------------*/
98
+ /* Setup */
99
+ /*------------------------------------------------------------------------*/
100
+
101
+ /* -------------- Props ------------- */
102
+
103
+ // Destructure all props
104
+ const {
105
+ name,
106
+ text,
107
+ label,
108
+ labelIcon,
109
+ minLabelWidthRem,
110
+ multiline,
111
+ numVisibleLines = 10,
112
+ } = props;
113
+
114
+ /* -------------- State ------------- */
115
+
116
+ // Initial state
117
+ const initialState: State = {
118
+ recentlyCopied: false,
119
+ };
120
+
121
+ // Initialize state
122
+ const [state, dispatch] = useReducer(reducer, initialState);
123
+
124
+ // Destructure common state
125
+ const {
126
+ recentlyCopied,
127
+ } = state;
128
+
129
+ /*------------------------------------------------------------------------*/
130
+ /* Component Functions */
131
+ /*------------------------------------------------------------------------*/
132
+
133
+ // Determine the id for the copiable text field
134
+ const copiableFieldClassName = `CopiableBox-text-box-${name}`;
135
+
136
+ /**
137
+ * Perform a copy
138
+ * @author Gabe Abrams
139
+ */
140
+ const performCopy = async () => {
141
+ // Write to clipboard
142
+ let copyFailed = false;
143
+ try {
144
+ await navigator.clipboard.writeText(text);
145
+ } catch (err) {
146
+ copyFailed = true;
147
+ }
148
+
149
+ // Try copy again if it failed
150
+ if (copyFailed) {
151
+ try {
152
+ const input = (
153
+ document.getElementsByClassName(copiableFieldClassName)[0]
154
+ ) as HTMLInputElement;
155
+ input.focus();
156
+ input.select();
157
+ document.execCommand('copy');
158
+ input.blur();
159
+ } catch (err) {
160
+ return alert(
161
+ 'Unable to copy',
162
+ 'Oops! We couldn\'t copy that to the clipboard. Please copy the text manually.',
163
+ );
164
+ }
165
+ }
166
+
167
+ // Show copied notice
168
+ dispatch({
169
+ type: ActionType.IndicateRecentlyCopied,
170
+ });
171
+
172
+ // Wait a moment
173
+ await waitMs(4000);
174
+
175
+ // Hide copied notice
176
+ dispatch({
177
+ type: ActionType.ClearRecentlyCopiedStatus,
178
+ });
179
+ };
180
+
181
+ /*------------------------------------------------------------------------*/
182
+ /* Render */
183
+ /*------------------------------------------------------------------------*/
184
+
185
+ /*----------------------------------------*/
186
+ /* Main UI */
187
+ /*----------------------------------------*/
188
+
189
+ return (
190
+ <div className="input-group mb-2">
191
+ {/* Label */}
192
+ <span
193
+ className="input-group-text"
194
+ style={{
195
+ minWidth: (
196
+ minLabelWidthRem
197
+ ? `${minLabelWidthRem}rem`
198
+ : undefined
199
+ ),
200
+ }}
201
+ >
202
+ {labelIcon && (
203
+ <FontAwesomeIcon
204
+ icon={labelIcon}
205
+ className="me-1"
206
+ />
207
+ )}
208
+ {label}
209
+ </span>
210
+
211
+ {/* Text */}
212
+ {
213
+ multiline
214
+ ? (
215
+ <textarea
216
+ className={`${copiableFieldClassName} CopiableBox-text-multiline form-control bg-white text-dark`}
217
+ value={text}
218
+ aria-label={`${label} text`}
219
+ rows={numVisibleLines}
220
+ readOnly
221
+ />
222
+ )
223
+ : (
224
+ <input
225
+ type="text"
226
+ className={`${copiableFieldClassName} CopiableBox-text-single-line form-control bg-white text-dark`}
227
+ value={text}
228
+ aria-label={`${label} text`}
229
+ readOnly
230
+ />
231
+ )
232
+ }
233
+
234
+ <button
235
+ className="btn btn-secondary"
236
+ type="button"
237
+ aria-label={`copy ${label} to the clipboard`}
238
+ disabled={recentlyCopied}
239
+ style={{
240
+ minWidth: '5.2rem',
241
+ }}
242
+ onClick={performCopy}
243
+ >
244
+ {
245
+ recentlyCopied
246
+ ? 'Copied!'
247
+ : (
248
+ <span>
249
+ <FontAwesomeIcon
250
+ icon={faClipboard}
251
+ className="me-1"
252
+ />
253
+ Copy
254
+ </span>
255
+ )
256
+ }
257
+ </button>
258
+ </div>
259
+ );
260
+ };
261
+
262
+ /*------------------------------------------------------------------------*/
263
+ /* Wrap Up */
264
+ /*------------------------------------------------------------------------*/
265
+
266
+ // Export component
267
+ export default CopiableBox;
@@ -0,0 +1,171 @@
1
+ /**
2
+ * Failure x mark that pops into view
3
+ * @author Gabe Abrams
4
+ */
5
+
6
+ // Import React
7
+ import React from 'react';
8
+
9
+ /*------------------------------------------------------------------------*/
10
+ /* Types */
11
+ /*------------------------------------------------------------------------*/
12
+
13
+ // Props definition
14
+ type Props = {
15
+ // Size of x mark in rem
16
+ sizeRem?: number,
17
+ // Bootstrap variant of circle
18
+ circleVariant?: string,
19
+ // Bootstrap variant of x mark
20
+ xVariant?: string,
21
+ };
22
+
23
+ /*------------------------------------------------------------------------*/
24
+ /* Style */
25
+ /*------------------------------------------------------------------------*/
26
+
27
+ const style = `
28
+ .PopFailureMark-outer-container {
29
+ position: relative;
30
+ display: inline-block;
31
+ border-radius: 50%;
32
+
33
+ animation-name: PopFailureMark-outer-container;
34
+ animation-duration: 0.8s;
35
+ animation-fill-mode: both;
36
+ animation-iteration-count: 1;
37
+ animation-timing-function: ease-out;
38
+ }
39
+ @keyframes PopFailureMark-outer-container {
40
+ 0% {
41
+ opacity: 0;
42
+ transform: scale(1.5);
43
+ filter: saturate(0);
44
+ }
45
+ 80.7% {
46
+ opacity: 1;
47
+ transform: scale(1);
48
+ filter: saturate(0);
49
+ }
50
+ 100% {
51
+ opacity: 1;
52
+ transform: scale(1);
53
+ filter: saturate(1);
54
+ }
55
+ }
56
+
57
+ .PopFailureMark-x-stroke-1 {
58
+ position: absolute;
59
+ left: 25%;
60
+ top: 19%;
61
+
62
+ display: inline-block;
63
+ height: 16%;
64
+ width: 70%;
65
+
66
+ transform-origin: left;
67
+
68
+ animation-name: PopFailureMark-x-stroke-1;
69
+ animation-duration: 0.3s;
70
+ animation-delay: 0.3s;
71
+ animation-fill-mode: both;
72
+ animation-iteration-count: 1;
73
+ animation-timing-function: ease-in;
74
+ }
75
+ @keyframes PopFailureMark-x-stroke-1 {
76
+ 0% {
77
+ transform: rotate(45deg) scaleX(0);
78
+ }
79
+ 100% {
80
+ transform: rotate(45deg) scaleX(1);
81
+ }
82
+ }
83
+
84
+ .PopFailureMark-x-stroke-2 {
85
+ position: absolute;
86
+ left: 75%;
87
+ top: 19%;
88
+
89
+ display: inline-block;
90
+ height: 16%;
91
+ width: 70%;
92
+
93
+ transform-origin: left;
94
+
95
+ animation-name: PopFailureMark-x-stroke-2;
96
+ animation-duration: 0.3s;
97
+ animation-delay: 0.6s;
98
+ animation-fill-mode: both;
99
+ animation-iteration-count: 1;
100
+ animation-timing-function: ease-out;
101
+ }
102
+ @keyframes PopFailureMark-x-stroke-2 {
103
+ 0% {
104
+ transform: rotate(135deg) scaleX(0);
105
+ }
106
+ 100% {
107
+ transform: rotate(135deg) scaleX(1);
108
+ }
109
+ }
110
+ `;
111
+
112
+ /*------------------------------------------------------------------------*/
113
+ /* Component */
114
+ /*------------------------------------------------------------------------*/
115
+
116
+ const PopFailureMark: React.FC<Props> = (props) => {
117
+ /*------------------------------------------------------------------------*/
118
+ /* Setup */
119
+ /*------------------------------------------------------------------------*/
120
+
121
+ /* -------------- Props ------------- */
122
+
123
+ // Destructure all props
124
+ const {
125
+ sizeRem = 3,
126
+ circleVariant = 'danger',
127
+ xVariant = 'white',
128
+ } = props;
129
+
130
+ /*------------------------------------------------------------------------*/
131
+ /* Render */
132
+ /*------------------------------------------------------------------------*/
133
+
134
+ /*----------------------------------------*/
135
+ /* Main UI */
136
+ /*----------------------------------------*/
137
+
138
+ return (
139
+ <div
140
+ className={`PopFailureMark-outer-container bg-${circleVariant}`}
141
+ style={{
142
+ width: `${sizeRem}rem`,
143
+ height: `${sizeRem}rem`,
144
+ }}
145
+ aria-label="mark indicating failure"
146
+ >
147
+ {/* Style */}
148
+ <style>{style}</style>
149
+ {/* Failure mark */}
150
+ <div
151
+ className={`PopFailureMark-x-stroke-1 bg-${xVariant}`}
152
+ style={{
153
+ borderRadius: `${sizeRem / 5}rem`,
154
+ }}
155
+ />
156
+ <div
157
+ className={`PopFailureMark-x-stroke-2 bg-${xVariant}`}
158
+ style={{
159
+ borderRadius: `${sizeRem / 5}rem`,
160
+ }}
161
+ />
162
+ </div>
163
+ );
164
+ };
165
+
166
+ /*------------------------------------------------------------------------*/
167
+ /* Wrap Up */
168
+ /*------------------------------------------------------------------------*/
169
+
170
+ // Export component
171
+ export default PopFailureMark;
@@ -0,0 +1,141 @@
1
+ /**
2
+ * Failure pending that pops into view
3
+ * @author Gabe Abrams
4
+ */
5
+
6
+ // Import React
7
+ import React from 'react';
8
+
9
+ // Import FontAwesome
10
+ import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
11
+ import { faHourglass } from '@fortawesome/free-solid-svg-icons';
12
+
13
+ /*------------------------------------------------------------------------*/
14
+ /* Types */
15
+ /*------------------------------------------------------------------------*/
16
+
17
+ // Props definition
18
+ type Props = {
19
+ // Size of pending in rem
20
+ sizeRem?: number,
21
+ // Bootstrap variant of circle
22
+ circleVariant?: string,
23
+ // Bootstrap variant of pending
24
+ hourglassVariant?: string,
25
+ };
26
+
27
+ /*------------------------------------------------------------------------*/
28
+ /* Style */
29
+ /*------------------------------------------------------------------------*/
30
+
31
+ const style = `
32
+ .PopPendingMark-outer-container {
33
+ position: relative;
34
+ display: inline-block;
35
+ border-radius: 50%;
36
+
37
+ animation-name: PopPendingMark-outer-container;
38
+ animation-duration: 0.8s;
39
+ animation-fill-mode: both;
40
+ animation-iteration-count: 1;
41
+ animation-timing-function: ease-out;
42
+ }
43
+ @keyframes PopPendingMark-outer-container {
44
+ 0% {
45
+ opacity: 0;
46
+ transform: scale(1.5);
47
+ filter: saturate(0);
48
+ }
49
+ 80.7% {
50
+ opacity: 1;
51
+ transform: scale(1);
52
+ filter: saturate(0);
53
+ }
54
+ 100% {
55
+ opacity: 1;
56
+ transform: scale(1);
57
+ filter: saturate(1);
58
+ }
59
+ }
60
+
61
+ .PopPendingMark-hourglass {
62
+ position: absolute;
63
+ left: 28%;
64
+ top: 21%;
65
+
66
+ animation-name: PopPendingMark-pending;
67
+ animation-duration: 0.3s;
68
+ animation-delay: 0.3s;
69
+ animation-fill-mode: both;
70
+ animation-iteration-count: 1;
71
+ animation-timing-function: ease-in;
72
+ }
73
+ @keyframes PopPendingMark-pending {
74
+ 0% {
75
+ transform: scale(0.7) rotate(30deg);
76
+ opacity: 0;
77
+ }
78
+ 100% {
79
+ transform: scale(1) rotate(0);
80
+ opacity: 1;
81
+ }
82
+ }
83
+ `;
84
+
85
+ /*------------------------------------------------------------------------*/
86
+ /* Component */
87
+ /*------------------------------------------------------------------------*/
88
+
89
+ const PopPendingMark: React.FC<Props> = (props) => {
90
+ /*------------------------------------------------------------------------*/
91
+ /* Setup */
92
+ /*------------------------------------------------------------------------*/
93
+
94
+ /* -------------- Props ------------- */
95
+
96
+ // Destructure all props
97
+ const {
98
+ sizeRem = 3,
99
+ circleVariant = 'warning',
100
+ hourglassVariant = 'white',
101
+ } = props;
102
+
103
+ /*------------------------------------------------------------------------*/
104
+ /* Render */
105
+ /*------------------------------------------------------------------------*/
106
+
107
+ /*----------------------------------------*/
108
+ /* Main UI */
109
+ /*----------------------------------------*/
110
+
111
+ return (
112
+ <div
113
+ className={`PopPendingMark-outer-container bg-${circleVariant}`}
114
+ style={{
115
+ width: `${sizeRem}rem`,
116
+ height: `${sizeRem}rem`,
117
+ }}
118
+ aria-label="mark indicating that the item is pending"
119
+ >
120
+ {/* Style */}
121
+ <style>{style}</style>
122
+ {/* Pending mark */}
123
+ <div>
124
+ <FontAwesomeIcon
125
+ icon={faHourglass}
126
+ className={`PopPendingMark-hourglass text-${hourglassVariant}`}
127
+ style={{
128
+ fontSize: `${sizeRem * 0.6}rem`,
129
+ }}
130
+ />
131
+ </div>
132
+ </div>
133
+ );
134
+ };
135
+
136
+ /*------------------------------------------------------------------------*/
137
+ /* Wrap Up */
138
+ /*------------------------------------------------------------------------*/
139
+
140
+ // Export component
141
+ export default PopPendingMark;
@@ -33,7 +33,7 @@ const getHumanReadableDate = (dateOrTimestamp?: Date | number) => {
33
33
  const currYear = getTimeInfoInET().year;
34
34
 
35
35
  // Create start of description
36
- let description = `${monthMap[month as keyof typeof monthMap]} ${getOrdinal(day)}`;
36
+ let description = `${monthMap[month as keyof typeof monthMap]} ${day}${getOrdinal(day)}`;
37
37
 
38
38
  // Add on year if it's different
39
39
  if (year !== currYear) {
package/src/index.ts CHANGED
@@ -10,6 +10,9 @@ import ButtonInputGroup from './components/ButtonInputGroup';
10
10
  import SimpleDateChooser from './components/SimpleDateChooser';
11
11
  import Drawer from './components/Drawer';
12
12
  import PopSuccessMark from './components/PopSuccessMark';
13
+ import PopFailureMark from './components/PopFailureMark';
14
+ import PopPendingMark from './components/PopPendingMark';
15
+ import CopiableBox from './components/CopiableBox';
13
16
 
14
17
  // Import errors
15
18
  import ErrorWithCode from './errors/ErrorWithCode';
@@ -63,6 +66,9 @@ export {
63
66
  SimpleDateChooser,
64
67
  Drawer,
65
68
  PopSuccessMark,
69
+ PopFailureMark,
70
+ PopPendingMark,
71
+ CopiableBox,
66
72
  // Global functions
67
73
  alert,
68
74
  confirm,