dce-reactkit 3.5.2 → 3.5.4
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.
- package/dist/cjs/index.js +500 -135
- package/dist/cjs/index.js.map +1 -1
- package/dist/cjs/types/components/Tooltip.d.ts +15 -0
- package/dist/cjs/types/index.d.ts +2 -1
- package/dist/esm/index.js +501 -137
- package/dist/esm/index.js.map +1 -1
- package/dist/esm/types/components/Tooltip.d.ts +15 -0
- package/dist/esm/types/index.d.ts +2 -1
- package/dist/index.d.ts +52 -37
- package/package.json +2 -1
- package/src/components/CopiableBox.tsx +5 -4
- package/src/components/Tooltip.tsx +567 -0
- package/src/helpers/compareArraysByProp.test.ts +45 -0
- package/src/helpers/compareArraysByProp.ts +2 -5
- package/src/helpers/getTimeInfoInET.test.ts +2 -2
- package/src/helpers/validators/validateString.test.ts +0 -2
- package/src/index.ts +2 -0
|
@@ -0,0 +1,567 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Simple tooltip component
|
|
3
|
+
* @author Gabe Abrams
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
// Import React
|
|
7
|
+
import React, { useEffect, useReducer, useRef } from 'react';
|
|
8
|
+
|
|
9
|
+
// Import FontAwesome
|
|
10
|
+
import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
|
|
11
|
+
import { IconProp } from '@fortawesome/fontawesome-svg-core';
|
|
12
|
+
import { faCaretDown } from '@fortawesome/free-solid-svg-icons';
|
|
13
|
+
|
|
14
|
+
/*------------------------------------------------------------------------*/
|
|
15
|
+
/* -------------------------------- Types ------------------------------- */
|
|
16
|
+
/*------------------------------------------------------------------------*/
|
|
17
|
+
|
|
18
|
+
// Props definition
|
|
19
|
+
type Props = {
|
|
20
|
+
// Icon to show next to the text
|
|
21
|
+
icon?: IconProp,
|
|
22
|
+
// Text of the tooltip
|
|
23
|
+
text: string,
|
|
24
|
+
// Children to wrap in the tooltip
|
|
25
|
+
children: React.ReactNode,
|
|
26
|
+
// If true, tooltip is wider
|
|
27
|
+
wide?: boolean,
|
|
28
|
+
// If true, tooltip is thinner
|
|
29
|
+
thin?: boolean,
|
|
30
|
+
};
|
|
31
|
+
|
|
32
|
+
/*------------------------------------------------------------------------*/
|
|
33
|
+
/* ------------------------------ Constants ----------------------------- */
|
|
34
|
+
/*------------------------------------------------------------------------*/
|
|
35
|
+
|
|
36
|
+
// Tooltip widths
|
|
37
|
+
const TOOLTIP_WIDTH_THIN_REM = 5;
|
|
38
|
+
const TOOLTIP_WIDTH_NORMAL_REM = 7;
|
|
39
|
+
const TOOLTIP_WIDTH_WIDE_REM = 10;
|
|
40
|
+
|
|
41
|
+
// Tooltip border radius
|
|
42
|
+
const TOOLTIP_BORDER_RADIUS_REM = 0.3;
|
|
43
|
+
|
|
44
|
+
/*------------------------------------------------------------------------*/
|
|
45
|
+
/* -------------------------------- Style ------------------------------- */
|
|
46
|
+
/*------------------------------------------------------------------------*/
|
|
47
|
+
|
|
48
|
+
const style = `
|
|
49
|
+
/* Container for contents and tooltip */
|
|
50
|
+
.Tooltip-outer-container {
|
|
51
|
+
display: inline-block;
|
|
52
|
+
position: relative;
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
/* Container for just tooltip */
|
|
56
|
+
.Tooltip-tooltip-container {
|
|
57
|
+
/* Position in middle but take no space */
|
|
58
|
+
/* (so layout of page is unaffected) */
|
|
59
|
+
position: absolute;
|
|
60
|
+
display: inline-block;
|
|
61
|
+
top: 0;
|
|
62
|
+
left: 50%;
|
|
63
|
+
height: 0;
|
|
64
|
+
width: 0;
|
|
65
|
+
overflow: visible;
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
/* Tooltip */
|
|
69
|
+
.Tooltip-tooltip {
|
|
70
|
+
/* Position above item */
|
|
71
|
+
display: inline-block;
|
|
72
|
+
position: absolute;
|
|
73
|
+
top: -2.3rem;
|
|
74
|
+
height: 1.8rem;
|
|
75
|
+
z-index: 1;
|
|
76
|
+
|
|
77
|
+
/* Use standard black font */
|
|
78
|
+
font-size: 1rem;
|
|
79
|
+
color: black;
|
|
80
|
+
|
|
81
|
+
/* Non-interactive */
|
|
82
|
+
pointer-events: none;
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
/* Tooltip box (the rectangle surrounding the text) */
|
|
86
|
+
.Tooltip-box,
|
|
87
|
+
.Tooltip-box-shadow,
|
|
88
|
+
.Tooltip-box-highlight {
|
|
89
|
+
/* Fill all space with rectangle */
|
|
90
|
+
position: absolute;
|
|
91
|
+
left: 0;
|
|
92
|
+
top: 0;
|
|
93
|
+
display: inline-block;
|
|
94
|
+
width: 100%;
|
|
95
|
+
height: 100%;
|
|
96
|
+
|
|
97
|
+
/* Rounded border */
|
|
98
|
+
border: 0.15rem solid black;
|
|
99
|
+
border-radius: ${TOOLTIP_BORDER_RADIUS_REM}rem;
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
/* Tooltip highlight (the bright background to the text) */
|
|
103
|
+
.Tooltip-box-highlight {
|
|
104
|
+
/* Bootstrap warning variant background */
|
|
105
|
+
background-color: rgb(255, 193, 7);
|
|
106
|
+
border-radius: ${TOOLTIP_BORDER_RADIUS_REM}rem;
|
|
107
|
+
|
|
108
|
+
/* Place on top */
|
|
109
|
+
z-index: -1;
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
/* Tooltip box (the black rectangle) */
|
|
113
|
+
.Tooltip-box {
|
|
114
|
+
/* Plain black background */
|
|
115
|
+
background-color: black;
|
|
116
|
+
|
|
117
|
+
/* Place in middle */
|
|
118
|
+
z-index: -2;
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
/* Tooltip box shadow (the white shadow beneath the box) */
|
|
122
|
+
.Tooltip-box-shadow {
|
|
123
|
+
/* White shadow */
|
|
124
|
+
box-shadow: 0 0 0.3rem white;
|
|
125
|
+
|
|
126
|
+
/* Place beneath */
|
|
127
|
+
z-index: -3;
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
/* Tooltip contents (the text and icon) */
|
|
131
|
+
.Tooltip-contents {
|
|
132
|
+
/* Center text, don't allow overflow */
|
|
133
|
+
white-space: nowrap;
|
|
134
|
+
overflow: hidden;
|
|
135
|
+
text-align: center;
|
|
136
|
+
text-overflow: ellipsis;
|
|
137
|
+
display: flex;
|
|
138
|
+
align-items: center;
|
|
139
|
+
justify-content: center;
|
|
140
|
+
|
|
141
|
+
/* Space within parent */
|
|
142
|
+
z-index: 1;
|
|
143
|
+
padding-left: 0.2rem;
|
|
144
|
+
padding-right: 0.2rem;
|
|
145
|
+
height: 100%;
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
/* Tooltip text */
|
|
149
|
+
.Tooltip-text {
|
|
150
|
+
/* Center text, add ellipsis */
|
|
151
|
+
overflow: hidden;
|
|
152
|
+
text-align: center;
|
|
153
|
+
text-overflow: ellipsis;
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
/* Classes to switch between tooltip visible/hidden */
|
|
157
|
+
.Tooltip-tooltip-visible-true {
|
|
158
|
+
opacity: 1;
|
|
159
|
+
}
|
|
160
|
+
.Tooltip-tooltip-visible-false {
|
|
161
|
+
opacity: 0;
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
/* Classes to switch between tooltip sizes */
|
|
165
|
+
.Tooltip-size-thin {
|
|
166
|
+
width: ${TOOLTIP_WIDTH_THIN_REM}rem;
|
|
167
|
+
left: calc(50% - ${TOOLTIP_WIDTH_THIN_REM / 2}rem);
|
|
168
|
+
}
|
|
169
|
+
.Tooltip-size-normal {
|
|
170
|
+
width: ${TOOLTIP_WIDTH_NORMAL_REM}rem;
|
|
171
|
+
left: calc(50% - ${TOOLTIP_WIDTH_NORMAL_REM / 2}rem);
|
|
172
|
+
}
|
|
173
|
+
.Tooltip-size-wide {
|
|
174
|
+
width: ${TOOLTIP_WIDTH_WIDE_REM}rem;
|
|
175
|
+
left: calc(50% - ${TOOLTIP_WIDTH_WIDE_REM / 2}rem);
|
|
176
|
+
}
|
|
177
|
+
|
|
178
|
+
/* Arrow (beneath tooltip) */
|
|
179
|
+
.Tooltip-arrow,
|
|
180
|
+
.Tooltip-arrow-shadow {
|
|
181
|
+
/* Position beneath tooltip */
|
|
182
|
+
position: absolute;
|
|
183
|
+
top: -1.5rem;
|
|
184
|
+
font-size: 1.5rem;
|
|
185
|
+
left: calc(50% - 0.5rem);
|
|
186
|
+
width: 0;
|
|
187
|
+
display: inline-block;
|
|
188
|
+
|
|
189
|
+
/* Non-interactive */
|
|
190
|
+
pointer-events: none;
|
|
191
|
+
}
|
|
192
|
+
|
|
193
|
+
/* Arrow itself */
|
|
194
|
+
.Tooltip-arrow {
|
|
195
|
+
color: black;
|
|
196
|
+
z-index: 4;
|
|
197
|
+
}
|
|
198
|
+
|
|
199
|
+
/* Arrow shadow */
|
|
200
|
+
.Tooltip-arrow-shadow {
|
|
201
|
+
z-index: -1;
|
|
202
|
+
|
|
203
|
+
/* Blur a white version of the caret as a shadow */
|
|
204
|
+
color: white;
|
|
205
|
+
filter: blur(0.2rem);
|
|
206
|
+
}
|
|
207
|
+
|
|
208
|
+
/* Classes to switch between arrow visible/hidden */
|
|
209
|
+
.Tooltip-arrow-visible-true {
|
|
210
|
+
opacity: 1;
|
|
211
|
+
}
|
|
212
|
+
.Tooltip-arrow-visible-false {
|
|
213
|
+
opacity: 0;
|
|
214
|
+
}
|
|
215
|
+
`;
|
|
216
|
+
|
|
217
|
+
/*------------------------------------------------------------------------*/
|
|
218
|
+
/* -------------------------------- State ------------------------------- */
|
|
219
|
+
/*------------------------------------------------------------------------*/
|
|
220
|
+
|
|
221
|
+
/* -------- State Definition -------- */
|
|
222
|
+
|
|
223
|
+
type State = {
|
|
224
|
+
// If true, the tooltip is visible
|
|
225
|
+
isVisible: boolean,
|
|
226
|
+
// Number of pixels to nudge the tooltip
|
|
227
|
+
nudgePx: number,
|
|
228
|
+
// If true, bottom left corner of tooltip should not be rounded
|
|
229
|
+
squareBottomLeft?: boolean,
|
|
230
|
+
// If true, bottom right corner of tooltip should not be rounded
|
|
231
|
+
squareBottomRight?: boolean,
|
|
232
|
+
};
|
|
233
|
+
|
|
234
|
+
/* ------------- Actions ------------ */
|
|
235
|
+
|
|
236
|
+
// Types of actions
|
|
237
|
+
enum ActionType {
|
|
238
|
+
// Make the tooltip visible
|
|
239
|
+
Show = 'Show',
|
|
240
|
+
// Make the tooltip invisible
|
|
241
|
+
Hide = 'Hide',
|
|
242
|
+
// Set nudge pixels
|
|
243
|
+
SetNudgePx = 'SetNudgePx',
|
|
244
|
+
// Set whether bottom left corner should be square
|
|
245
|
+
SetSquareBottomLeft = 'SetSquareBottomLeft',
|
|
246
|
+
// Set whether bottom right corner should be square
|
|
247
|
+
SetSquareBottomRight = 'SetSquareBottomRight',
|
|
248
|
+
}
|
|
249
|
+
|
|
250
|
+
// Action definitions
|
|
251
|
+
type Action = (
|
|
252
|
+
| {
|
|
253
|
+
// Action type
|
|
254
|
+
type: ActionType.SetNudgePx,
|
|
255
|
+
// Number of pixels to nudge the tooltip
|
|
256
|
+
nudgePx: number,
|
|
257
|
+
}
|
|
258
|
+
| {
|
|
259
|
+
// Action type
|
|
260
|
+
type: ActionType.SetSquareBottomLeft,
|
|
261
|
+
// If true, bottom left corner of tooltip should not be rounded
|
|
262
|
+
squareBottomLeft: boolean,
|
|
263
|
+
}
|
|
264
|
+
| {
|
|
265
|
+
// Action type
|
|
266
|
+
type: ActionType.SetSquareBottomRight,
|
|
267
|
+
// If true, bottom right corner of tooltip should not be rounded
|
|
268
|
+
squareBottomRight: boolean,
|
|
269
|
+
}
|
|
270
|
+
| {
|
|
271
|
+
// Action type
|
|
272
|
+
type: (
|
|
273
|
+
| ActionType.Show
|
|
274
|
+
| ActionType.Hide
|
|
275
|
+
),
|
|
276
|
+
}
|
|
277
|
+
);
|
|
278
|
+
|
|
279
|
+
/**
|
|
280
|
+
* Reducer that executes actions
|
|
281
|
+
* @author Gabe Abrams
|
|
282
|
+
* @param state current state
|
|
283
|
+
* @param action action to execute
|
|
284
|
+
*/
|
|
285
|
+
const reducer = (state: State, action: Action): State => {
|
|
286
|
+
switch (action.type) {
|
|
287
|
+
case ActionType.Show: {
|
|
288
|
+
return {
|
|
289
|
+
...state,
|
|
290
|
+
isVisible: true,
|
|
291
|
+
};
|
|
292
|
+
}
|
|
293
|
+
case ActionType.Hide: {
|
|
294
|
+
return {
|
|
295
|
+
...state,
|
|
296
|
+
isVisible: false,
|
|
297
|
+
nudgePx: 0,
|
|
298
|
+
};
|
|
299
|
+
}
|
|
300
|
+
case ActionType.SetNudgePx: {
|
|
301
|
+
return {
|
|
302
|
+
...state,
|
|
303
|
+
nudgePx: action.nudgePx,
|
|
304
|
+
};
|
|
305
|
+
}
|
|
306
|
+
case ActionType.SetSquareBottomLeft: {
|
|
307
|
+
return {
|
|
308
|
+
...state,
|
|
309
|
+
squareBottomLeft: action.squareBottomLeft,
|
|
310
|
+
};
|
|
311
|
+
}
|
|
312
|
+
case ActionType.SetSquareBottomRight: {
|
|
313
|
+
return {
|
|
314
|
+
...state,
|
|
315
|
+
squareBottomRight: action.squareBottomRight,
|
|
316
|
+
};
|
|
317
|
+
}
|
|
318
|
+
default: {
|
|
319
|
+
return state;
|
|
320
|
+
}
|
|
321
|
+
}
|
|
322
|
+
};
|
|
323
|
+
|
|
324
|
+
/*------------------------------------------------------------------------*/
|
|
325
|
+
/* ------------------------------ Component ----------------------------- */
|
|
326
|
+
/*------------------------------------------------------------------------*/
|
|
327
|
+
|
|
328
|
+
const Tooltip: React.FC<Props> = (props) => {
|
|
329
|
+
/*------------------------------------------------------------------------*/
|
|
330
|
+
/* -------------------------------- Setup ------------------------------- */
|
|
331
|
+
/*------------------------------------------------------------------------*/
|
|
332
|
+
|
|
333
|
+
/* -------------- Props ------------- */
|
|
334
|
+
|
|
335
|
+
// Destructure all props
|
|
336
|
+
const {
|
|
337
|
+
icon,
|
|
338
|
+
text,
|
|
339
|
+
children,
|
|
340
|
+
wide,
|
|
341
|
+
thin,
|
|
342
|
+
} = props;
|
|
343
|
+
|
|
344
|
+
/* -------------- State ------------- */
|
|
345
|
+
|
|
346
|
+
// Initial state
|
|
347
|
+
const initialState: State = {
|
|
348
|
+
isVisible: false,
|
|
349
|
+
nudgePx: 0,
|
|
350
|
+
};
|
|
351
|
+
|
|
352
|
+
// Initialize state
|
|
353
|
+
const [state, dispatch] = useReducer(reducer, initialState);
|
|
354
|
+
|
|
355
|
+
// Destructure common state
|
|
356
|
+
const {
|
|
357
|
+
isVisible,
|
|
358
|
+
nudgePx,
|
|
359
|
+
squareBottomLeft,
|
|
360
|
+
squareBottomRight,
|
|
361
|
+
} = state;
|
|
362
|
+
|
|
363
|
+
/* -------------- Refs -------------- */
|
|
364
|
+
|
|
365
|
+
// Initialize refs
|
|
366
|
+
const tooltipContainer = useRef<HTMLDivElement | null>(null);
|
|
367
|
+
const arrowElement = useRef<HTMLDivElement | null>(null);
|
|
368
|
+
|
|
369
|
+
/*------------------------------------------------------------------------*/
|
|
370
|
+
/* ------------------------- Lifecycle Functions ------------------------ */
|
|
371
|
+
/*------------------------------------------------------------------------*/
|
|
372
|
+
|
|
373
|
+
/**
|
|
374
|
+
* Update (also called on mount)
|
|
375
|
+
* @author Gabe Abrams
|
|
376
|
+
*/
|
|
377
|
+
useEffect(
|
|
378
|
+
() => {
|
|
379
|
+
// Update tooltip container position
|
|
380
|
+
if (tooltipContainer.current && isVisible) {
|
|
381
|
+
// Get the tooltip container location
|
|
382
|
+
const tooltipContainerRect = (
|
|
383
|
+
tooltipContainer
|
|
384
|
+
.current
|
|
385
|
+
.getBoundingClientRect()
|
|
386
|
+
);
|
|
387
|
+
|
|
388
|
+
// Get the window width
|
|
389
|
+
const windowWidth = window.innerWidth;
|
|
390
|
+
|
|
391
|
+
// Calculate new nudge value
|
|
392
|
+
let newNudgePx: number;
|
|
393
|
+
if (tooltipContainerRect.left < 0) {
|
|
394
|
+
// Tooltip is off the left side of the screen
|
|
395
|
+
newNudgePx = -1 * tooltipContainerRect.left;
|
|
396
|
+
} else if (tooltipContainerRect.right > windowWidth) {
|
|
397
|
+
// Tooltip is off the right side of the screen
|
|
398
|
+
newNudgePx = -1 * (tooltipContainerRect.right - windowWidth);
|
|
399
|
+
} else {
|
|
400
|
+
// Tooltip is in the middle of the screen
|
|
401
|
+
newNudgePx = 0;
|
|
402
|
+
}
|
|
403
|
+
|
|
404
|
+
// Update state
|
|
405
|
+
dispatch({
|
|
406
|
+
type: ActionType.SetNudgePx,
|
|
407
|
+
nudgePx: newNudgePx,
|
|
408
|
+
});
|
|
409
|
+
}
|
|
410
|
+
|
|
411
|
+
// Update border rounding
|
|
412
|
+
if (arrowElement.current && isVisible) {
|
|
413
|
+
// Get the arrow location
|
|
414
|
+
const arrowRect = (
|
|
415
|
+
arrowElement
|
|
416
|
+
.current
|
|
417
|
+
.getBoundingClientRect()
|
|
418
|
+
);
|
|
419
|
+
|
|
420
|
+
// REM in pixels given font size
|
|
421
|
+
const remInPixels = Number.parseInt(
|
|
422
|
+
getComputedStyle(document.documentElement).fontSize,
|
|
423
|
+
10,
|
|
424
|
+
);
|
|
425
|
+
|
|
426
|
+
// Set bottom left corner rounding
|
|
427
|
+
dispatch({
|
|
428
|
+
type: ActionType.SetSquareBottomLeft,
|
|
429
|
+
squareBottomLeft: (
|
|
430
|
+
arrowRect.left < (TOOLTIP_BORDER_RADIUS_REM * remInPixels)
|
|
431
|
+
),
|
|
432
|
+
});
|
|
433
|
+
|
|
434
|
+
// Set bottom right corner rounding
|
|
435
|
+
dispatch({
|
|
436
|
+
type: ActionType.SetSquareBottomRight,
|
|
437
|
+
squareBottomRight: (
|
|
438
|
+
arrowRect.right
|
|
439
|
+
> window.innerWidth - (TOOLTIP_BORDER_RADIUS_REM * remInPixels)
|
|
440
|
+
),
|
|
441
|
+
});
|
|
442
|
+
}
|
|
443
|
+
},
|
|
444
|
+
[isVisible],
|
|
445
|
+
);
|
|
446
|
+
|
|
447
|
+
/*------------------------------------------------------------------------*/
|
|
448
|
+
/* ------------------------------- Render ------------------------------- */
|
|
449
|
+
/*------------------------------------------------------------------------*/
|
|
450
|
+
|
|
451
|
+
/*----------------------------------------*/
|
|
452
|
+
/* --------------- Main UI -------------- */
|
|
453
|
+
/*----------------------------------------*/
|
|
454
|
+
|
|
455
|
+
// Determine size
|
|
456
|
+
let size = 'normal';
|
|
457
|
+
if (wide) {
|
|
458
|
+
size = 'wide';
|
|
459
|
+
} else if (thin) {
|
|
460
|
+
size = 'thin';
|
|
461
|
+
}
|
|
462
|
+
|
|
463
|
+
// Render
|
|
464
|
+
return (
|
|
465
|
+
<div
|
|
466
|
+
className="Tooltip-outer-container"
|
|
467
|
+
aria-label={text}
|
|
468
|
+
onMouseEnter={() => {
|
|
469
|
+
dispatch({
|
|
470
|
+
type: ActionType.Show,
|
|
471
|
+
});
|
|
472
|
+
}}
|
|
473
|
+
onMouseLeave={() => {
|
|
474
|
+
dispatch({
|
|
475
|
+
type: ActionType.Hide,
|
|
476
|
+
});
|
|
477
|
+
}}
|
|
478
|
+
onFocus={() => {
|
|
479
|
+
dispatch({
|
|
480
|
+
type: ActionType.Show,
|
|
481
|
+
});
|
|
482
|
+
}}
|
|
483
|
+
onBlur={() => {
|
|
484
|
+
dispatch({
|
|
485
|
+
type: ActionType.Hide,
|
|
486
|
+
});
|
|
487
|
+
}}
|
|
488
|
+
>
|
|
489
|
+
{/* Style */}
|
|
490
|
+
<style>
|
|
491
|
+
{style}
|
|
492
|
+
</style>
|
|
493
|
+
|
|
494
|
+
{/* Children */}
|
|
495
|
+
{children}
|
|
496
|
+
|
|
497
|
+
{/* Arrow */}
|
|
498
|
+
<div
|
|
499
|
+
className={`Tooltip-arrow Tooltip-arrow-visible-${isVisible ? 'true' : 'false'}`}
|
|
500
|
+
ref={arrowElement}
|
|
501
|
+
>
|
|
502
|
+
<FontAwesomeIcon
|
|
503
|
+
icon={faCaretDown}
|
|
504
|
+
/>
|
|
505
|
+
</div>
|
|
506
|
+
<div className={`Tooltip-arrow-shadow Tooltip-arrow-visible-${isVisible ? 'true' : 'false'}`}>
|
|
507
|
+
<FontAwesomeIcon
|
|
508
|
+
icon={faCaretDown}
|
|
509
|
+
/>
|
|
510
|
+
</div>
|
|
511
|
+
|
|
512
|
+
{/* Tooltip */}
|
|
513
|
+
<div
|
|
514
|
+
className="Tooltip-tooltip-container"
|
|
515
|
+
>
|
|
516
|
+
<div
|
|
517
|
+
className={`Tooltip-tooltip Tooltip-tooltip-visible-${isVisible ? 'true' : 'false'} Tooltip-size-${size}`}
|
|
518
|
+
role="tooltip"
|
|
519
|
+
ref={tooltipContainer}
|
|
520
|
+
style={{
|
|
521
|
+
transform: `translateX(${nudgePx}px)`,
|
|
522
|
+
}}
|
|
523
|
+
>
|
|
524
|
+
{/* Box */}
|
|
525
|
+
<div className="Tooltip-box-highlight" />
|
|
526
|
+
<div
|
|
527
|
+
className="Tooltip-box"
|
|
528
|
+
style={{
|
|
529
|
+
borderBottomLeftRadius: squareBottomLeft ? 0 : undefined,
|
|
530
|
+
borderBottomRightRadius: squareBottomRight ? 0 : undefined,
|
|
531
|
+
}}
|
|
532
|
+
/>
|
|
533
|
+
<div
|
|
534
|
+
className="Tooltip-box-shadow"
|
|
535
|
+
style={{
|
|
536
|
+
borderBottomLeftRadius: squareBottomLeft ? 0 : undefined,
|
|
537
|
+
borderBottomRightRadius: squareBottomRight ? 0 : undefined,
|
|
538
|
+
}}
|
|
539
|
+
/>
|
|
540
|
+
|
|
541
|
+
{/* Contents */}
|
|
542
|
+
<div className="Tooltip-contents">
|
|
543
|
+
{/* Icon */}
|
|
544
|
+
{icon && (
|
|
545
|
+
<FontAwesomeIcon
|
|
546
|
+
icon={icon}
|
|
547
|
+
className="me-1"
|
|
548
|
+
/>
|
|
549
|
+
)}
|
|
550
|
+
|
|
551
|
+
{/* Text */}
|
|
552
|
+
<span className="Tooltip-text">
|
|
553
|
+
{text}
|
|
554
|
+
</span>
|
|
555
|
+
</div>
|
|
556
|
+
</div>
|
|
557
|
+
</div>
|
|
558
|
+
</div>
|
|
559
|
+
);
|
|
560
|
+
};
|
|
561
|
+
|
|
562
|
+
/*------------------------------------------------------------------------*/
|
|
563
|
+
/* ------------------------------- Wrap Up ------------------------------ */
|
|
564
|
+
/*------------------------------------------------------------------------*/
|
|
565
|
+
|
|
566
|
+
// Export component
|
|
567
|
+
export default Tooltip;
|
|
@@ -99,4 +99,49 @@ describe('compareArraysByProp', () => {
|
|
|
99
99
|
const result = compareArraysByProp(a, b, 'id');
|
|
100
100
|
expect(result).toEqual(false);
|
|
101
101
|
});
|
|
102
|
+
|
|
103
|
+
it('should return true for two arrays with the same objects with multiple properties and multiple props', () => {
|
|
104
|
+
const a = [
|
|
105
|
+
{ id: 1, name: 'Alice', age: 25 },
|
|
106
|
+
{ id: 2, name: 'Bob', age: 30 },
|
|
107
|
+
{ id: 3, name: 'Charlie', age: 35 },
|
|
108
|
+
];
|
|
109
|
+
const b = [
|
|
110
|
+
{ id: 1, name: 'Alice', age: 25 },
|
|
111
|
+
{ id: 2, name: 'Bob', age: 30 },
|
|
112
|
+
{ id: 3, name: 'Charlie', age: 35 },
|
|
113
|
+
];
|
|
114
|
+
const result = compareArraysByProp(a, b, ['id', 'name']);
|
|
115
|
+
expect(result).toEqual(true);
|
|
116
|
+
});
|
|
117
|
+
|
|
118
|
+
it('should return true for two arrays with the same objects with multiple properties and multiple props in a different order', () => {
|
|
119
|
+
const a = [
|
|
120
|
+
{ id: 1, name: 'Alice', age: 25 },
|
|
121
|
+
{ id: 2, name: 'Bob', age: 30 },
|
|
122
|
+
{ id: 3, name: 'Charlie', age: 35 },
|
|
123
|
+
];
|
|
124
|
+
const b = [
|
|
125
|
+
{ id: 3, name: 'Charlie', age: 35 },
|
|
126
|
+
{ id: 1, name: 'Alice', age: 25 },
|
|
127
|
+
{ id: 2, name: 'Bob', age: 30 },
|
|
128
|
+
];
|
|
129
|
+
const result = compareArraysByProp(a, b, ['id', 'name']);
|
|
130
|
+
expect(result).toEqual(true);
|
|
131
|
+
});
|
|
132
|
+
|
|
133
|
+
it('should return false for two arrays with different objects with multiple properties and multiple props', () => {
|
|
134
|
+
const a = [
|
|
135
|
+
{ id: 1, name: 'Alice', age: 25 },
|
|
136
|
+
{ id: 2, name: 'Bob', age: 30 },
|
|
137
|
+
{ id: 3, name: 'Charlie', age: 35 },
|
|
138
|
+
];
|
|
139
|
+
const b = [
|
|
140
|
+
{ id: 1, name: 'Bob', age: 40 },
|
|
141
|
+
{ id: 2, name: 'Alice', age: 45 },
|
|
142
|
+
{ id: 3, name: 'Charlie', age: 50 },
|
|
143
|
+
];
|
|
144
|
+
const result = compareArraysByProp(a, b, ['id', 'name']);
|
|
145
|
+
expect(result).toEqual(false);
|
|
146
|
+
});
|
|
102
147
|
});
|
|
@@ -1,6 +1,3 @@
|
|
|
1
|
-
// Import shared helpers
|
|
2
|
-
import extractProp from './extractProp';
|
|
3
|
-
|
|
4
1
|
/**
|
|
5
2
|
* Compare two arrays of objects by only comparing the values in a specific
|
|
6
3
|
* property (e.g. compare user arrays by comparing their user.id values)
|
|
@@ -34,8 +31,8 @@ const compareArraysByProp = (
|
|
|
34
31
|
const matchingIndex = bCloned.findIndex((bItem) => {
|
|
35
32
|
// Compare based on all props
|
|
36
33
|
return props.every((propToCompareBy) => {
|
|
37
|
-
const aVal =
|
|
38
|
-
const bVal =
|
|
34
|
+
const aVal = (a[i] ?? {})[propToCompareBy];
|
|
35
|
+
const bVal = (bItem ?? {})[propToCompareBy];
|
|
39
36
|
return aVal === bVal;
|
|
40
37
|
});
|
|
41
38
|
});
|
|
@@ -38,7 +38,7 @@ describe('getTimeInfoInET', () => {
|
|
|
38
38
|
const expectedHour = 19;
|
|
39
39
|
const expectedHour12 = 7;
|
|
40
40
|
const expectedMinute = 0;
|
|
41
|
-
const expectedIsPM =
|
|
41
|
+
const expectedIsPM = true;
|
|
42
42
|
expect(result.timestamp).toEqual(expectedTimestamp);
|
|
43
43
|
expect(result.year).toEqual(expectedYear);
|
|
44
44
|
expect(result.month).toEqual(expectedMonth);
|
|
@@ -59,7 +59,7 @@ describe('getTimeInfoInET', () => {
|
|
|
59
59
|
const expectedHour = 19;
|
|
60
60
|
const expectedHour12 = 7;
|
|
61
61
|
const expectedMinute = 0;
|
|
62
|
-
const expectedIsPM =
|
|
62
|
+
const expectedIsPM = true;
|
|
63
63
|
expect(result.timestamp).toEqual(expectedTimestamp);
|
|
64
64
|
expect(result.year).toEqual(expectedYear);
|
|
65
65
|
expect(result.month).toEqual(expectedMonth);
|
|
@@ -167,8 +167,6 @@ test(
|
|
|
167
167
|
errorMessage: `${INVALID_STRING_ERRORS.MESSAGE_INTRO}${genCommaList(triple.error)}.`,
|
|
168
168
|
};
|
|
169
169
|
|
|
170
|
-
console.log(invalidResponse.errorMessage);
|
|
171
|
-
|
|
172
170
|
expect(validateString(triple.input, triple.reqs)).toStrictEqual(invalidResponse);
|
|
173
171
|
});
|
|
174
172
|
},
|
package/src/index.ts
CHANGED
|
@@ -18,6 +18,7 @@ import LogReviewer from './components/LogReviewer';
|
|
|
18
18
|
import IntelliTable from './components/IntelliTable';
|
|
19
19
|
import CSVDownloadButton from './components/CSVDownloadButton';
|
|
20
20
|
import DBEntryManagerPanel from './components/DBEntryManagerPanel';
|
|
21
|
+
import Tooltip from './components/Tooltip';
|
|
21
22
|
|
|
22
23
|
// Import errors
|
|
23
24
|
import ErrorWithCode from './errors/ErrorWithCode';
|
|
@@ -115,6 +116,7 @@ export {
|
|
|
115
116
|
IntelliTable,
|
|
116
117
|
CSVDownloadButton,
|
|
117
118
|
DBEntryManagerPanel,
|
|
119
|
+
Tooltip,
|
|
118
120
|
// Global functions
|
|
119
121
|
alert,
|
|
120
122
|
confirm,
|