dce-reactkit 3.0.41 → 3.0.44

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,296 @@
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
+ // If defined, text box becomes clickable and this is the handler
37
+ onClick?: () => void,
38
+ };
39
+
40
+ /*------------------------------------------------------------------------*/
41
+ /* State */
42
+ /*------------------------------------------------------------------------*/
43
+
44
+ /* -------- State Definition -------- */
45
+
46
+ type State = {
47
+ // True if text was recently copied
48
+ recentlyCopied: boolean,
49
+ };
50
+
51
+ /* ------------- Actions ------------ */
52
+
53
+ // Types of actions
54
+ enum ActionType {
55
+ // Indicate that the text was recently copied
56
+ IndicateRecentlyCopied = 'indicate-recently-copied',
57
+ // Clear the status
58
+ ClearRecentlyCopiedStatus = 'clear-recently-copied-status',
59
+ }
60
+
61
+ // Action definitions
62
+ type Action = {
63
+ // Action type
64
+ type: (
65
+ | ActionType.IndicateRecentlyCopied
66
+ | ActionType.ClearRecentlyCopiedStatus
67
+ ),
68
+ };
69
+
70
+ /**
71
+ * Reducer that executes actions
72
+ * @author Gabe Abrams
73
+ * @param state current state
74
+ * @param action action to execute
75
+ */
76
+ const reducer = (state: State, action: Action): State => {
77
+ switch (action.type) {
78
+ case ActionType.IndicateRecentlyCopied: {
79
+ return {
80
+ recentlyCopied: true,
81
+ };
82
+ }
83
+ case ActionType.ClearRecentlyCopiedStatus: {
84
+ return {
85
+ recentlyCopied: false,
86
+ };
87
+ }
88
+ default: {
89
+ return state;
90
+ }
91
+ }
92
+ };
93
+
94
+ /*------------------------------------------------------------------------*/
95
+ /* Component */
96
+ /*------------------------------------------------------------------------*/
97
+
98
+ const CopiableBox: React.FC<Props> = (props) => {
99
+ /*------------------------------------------------------------------------*/
100
+ /* Setup */
101
+ /*------------------------------------------------------------------------*/
102
+
103
+ /* -------------- Props ------------- */
104
+
105
+ // Destructure all props
106
+ const {
107
+ name,
108
+ text,
109
+ label,
110
+ labelIcon,
111
+ minLabelWidthRem,
112
+ multiline,
113
+ numVisibleLines = 10,
114
+ onClick,
115
+ } = props;
116
+
117
+ /* -------------- State ------------- */
118
+
119
+ // Initial state
120
+ const initialState: State = {
121
+ recentlyCopied: false,
122
+ };
123
+
124
+ // Initialize state
125
+ const [state, dispatch] = useReducer(reducer, initialState);
126
+
127
+ // Destructure common state
128
+ const {
129
+ recentlyCopied,
130
+ } = state;
131
+
132
+ /*------------------------------------------------------------------------*/
133
+ /* Component Functions */
134
+ /*------------------------------------------------------------------------*/
135
+
136
+ // Determine the id for the copiable text field
137
+ const copiableFieldClassName = `CopiableBox-text-box-${name}`;
138
+
139
+ /**
140
+ * Perform a copy
141
+ * @author Gabe Abrams
142
+ */
143
+ const performCopy = async () => {
144
+ // Write to clipboard
145
+ let copyFailed = false;
146
+ try {
147
+ await navigator.clipboard.writeText(text);
148
+ } catch (err) {
149
+ copyFailed = true;
150
+ }
151
+
152
+ // Try copy again if it failed
153
+ if (copyFailed) {
154
+ try {
155
+ const input = (
156
+ document.getElementsByClassName(copiableFieldClassName)[0]
157
+ ) as HTMLInputElement;
158
+ input.focus();
159
+ input.select();
160
+ document.execCommand('copy');
161
+ input.blur();
162
+ } catch (err) {
163
+ return alert(
164
+ 'Unable to copy',
165
+ 'Oops! We couldn\'t copy that to the clipboard. Please copy the text manually.',
166
+ );
167
+ }
168
+ }
169
+
170
+ // Show copied notice
171
+ dispatch({
172
+ type: ActionType.IndicateRecentlyCopied,
173
+ });
174
+
175
+ // Wait a moment
176
+ await waitMs(4000);
177
+
178
+ // Hide copied notice
179
+ dispatch({
180
+ type: ActionType.ClearRecentlyCopiedStatus,
181
+ });
182
+ };
183
+
184
+ /*------------------------------------------------------------------------*/
185
+ /* Render */
186
+ /*------------------------------------------------------------------------*/
187
+
188
+ /*----------------------------------------*/
189
+ /* Main UI */
190
+ /*----------------------------------------*/
191
+
192
+ return (
193
+ <div className="input-group mb-2">
194
+ {/* Label */}
195
+ <span
196
+ className="input-group-text"
197
+ style={{
198
+ minWidth: (
199
+ minLabelWidthRem
200
+ ? `${minLabelWidthRem}rem`
201
+ : undefined
202
+ ),
203
+ }}
204
+ >
205
+ {labelIcon && (
206
+ <FontAwesomeIcon
207
+ icon={labelIcon}
208
+ className="me-1"
209
+ />
210
+ )}
211
+ {label}
212
+ </span>
213
+
214
+ {/* Text */}
215
+ {
216
+ multiline
217
+ ? (
218
+ <textarea
219
+ className={`${copiableFieldClassName} CopiableBox-text-multiline form-control bg-white text-dark`}
220
+ value={text}
221
+ aria-label={`${label} text`}
222
+ rows={numVisibleLines}
223
+ onClick={onClick}
224
+ style={{
225
+ cursor: (
226
+ onClick
227
+ ? 'pointer'
228
+ : 'default'
229
+ ),
230
+ textDecoration: (
231
+ onClick
232
+ ? 'underline'
233
+ : undefined
234
+ ),
235
+ }}
236
+ readOnly
237
+ />
238
+ )
239
+ : (
240
+ <input
241
+ type="text"
242
+ className={`${copiableFieldClassName} CopiableBox-text-single-line form-control bg-white text-dark`}
243
+ value={text}
244
+ aria-label={`${label} text`}
245
+ onClick={onClick}
246
+ style={{
247
+ cursor: (
248
+ onClick
249
+ ? 'pointer'
250
+ : 'default'
251
+ ),
252
+ textDecoration: (
253
+ onClick
254
+ ? 'underline'
255
+ : undefined
256
+ ),
257
+ }}
258
+ readOnly
259
+ />
260
+ )
261
+ }
262
+
263
+ <button
264
+ className="btn btn-secondary"
265
+ type="button"
266
+ aria-label={`copy ${label} to the clipboard`}
267
+ disabled={recentlyCopied}
268
+ style={{
269
+ minWidth: '5.2rem',
270
+ }}
271
+ onClick={performCopy}
272
+ >
273
+ {
274
+ recentlyCopied
275
+ ? 'Copied!'
276
+ : (
277
+ <span>
278
+ <FontAwesomeIcon
279
+ icon={faClipboard}
280
+ className="me-1"
281
+ />
282
+ Copy
283
+ </span>
284
+ )
285
+ }
286
+ </button>
287
+ </div>
288
+ );
289
+ };
290
+
291
+ /*------------------------------------------------------------------------*/
292
+ /* Wrap Up */
293
+ /*------------------------------------------------------------------------*/
294
+
295
+ // Export component
296
+ 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;
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,