dce-reactkit 3.0.42 → 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;
package/src/index.ts CHANGED
@@ -12,6 +12,7 @@ import Drawer from './components/Drawer';
12
12
  import PopSuccessMark from './components/PopSuccessMark';
13
13
  import PopFailureMark from './components/PopFailureMark';
14
14
  import PopPendingMark from './components/PopPendingMark';
15
+ import CopiableBox from './components/CopiableBox';
15
16
 
16
17
  // Import errors
17
18
  import ErrorWithCode from './errors/ErrorWithCode';
@@ -67,6 +68,7 @@ export {
67
68
  PopSuccessMark,
68
69
  PopFailureMark,
69
70
  PopPendingMark,
71
+ CopiableBox,
70
72
  // Global functions
71
73
  alert,
72
74
  confirm,