dce-reactkit 3.6.7 → 3.6.9
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 +348 -142
- package/dist/cjs/index.js.map +1 -1
- package/dist/cjs/types/components/MultiSwitch.d.ts +24 -0
- package/dist/cjs/types/components/Tooltip.d.ts +1 -0
- package/dist/cjs/types/index.d.ts +2 -1
- package/dist/esm/index.js +348 -143
- package/dist/esm/index.js.map +1 -1
- package/dist/esm/types/components/MultiSwitch.d.ts +24 -0
- package/dist/esm/types/components/Tooltip.d.ts +1 -0
- package/dist/esm/types/index.d.ts +2 -1
- package/dist/index.d.ts +67 -43
- package/package.json +1 -1
- package/src/components/MultiSwitch.tsx +347 -0
- package/src/components/Tooltip.tsx +4 -2
- package/src/index.ts +2 -0
|
@@ -0,0 +1,347 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* A switch with multiple options for selection
|
|
3
|
+
* @author Alessandra De Lucas
|
|
4
|
+
* @author Gabe Abrams
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
// Import React
|
|
8
|
+
import React, { useReducer } from 'react';
|
|
9
|
+
|
|
10
|
+
// Import FontAwesome
|
|
11
|
+
import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
|
|
12
|
+
import { IconProp } from '@fortawesome/fontawesome-svg-core';
|
|
13
|
+
|
|
14
|
+
/*------------------------------------------------------------------------*/
|
|
15
|
+
/* -------------------------------- Types ------------------------------- */
|
|
16
|
+
/*------------------------------------------------------------------------*/
|
|
17
|
+
|
|
18
|
+
// Props definition
|
|
19
|
+
type Props = {
|
|
20
|
+
// Array of all options for multi switch
|
|
21
|
+
options: Option[],
|
|
22
|
+
// The ID of the selected option
|
|
23
|
+
selectedOptionId: string,
|
|
24
|
+
/**
|
|
25
|
+
* A handler to call when the switch is changed
|
|
26
|
+
* @param selectedOptionId Updated option when switch is changed
|
|
27
|
+
*/
|
|
28
|
+
onChange: (selectedOptionId: string) => void,
|
|
29
|
+
// The height of the multi switch
|
|
30
|
+
heightRem?: number,
|
|
31
|
+
};
|
|
32
|
+
|
|
33
|
+
// Option definition
|
|
34
|
+
type Option = {
|
|
35
|
+
// Label
|
|
36
|
+
label: string,
|
|
37
|
+
// Icon
|
|
38
|
+
icon: IconProp,
|
|
39
|
+
// Unique ID
|
|
40
|
+
id: string,
|
|
41
|
+
};
|
|
42
|
+
|
|
43
|
+
/*------------------------------------------------------------------------*/
|
|
44
|
+
/* -------------------------------- State ------------------------------- */
|
|
45
|
+
/*------------------------------------------------------------------------*/
|
|
46
|
+
|
|
47
|
+
/* -------- State Definition -------- */
|
|
48
|
+
|
|
49
|
+
type State = {
|
|
50
|
+
// The option that the user is hovering over
|
|
51
|
+
hoveredOptionId?: string,
|
|
52
|
+
};
|
|
53
|
+
|
|
54
|
+
/* ------------- Actions ------------ */
|
|
55
|
+
|
|
56
|
+
// Types of actions
|
|
57
|
+
enum ActionType {
|
|
58
|
+
// Start hovering on an option
|
|
59
|
+
StartHover = 'StartHover',
|
|
60
|
+
// Stop hovering on an option
|
|
61
|
+
StopHover = 'StopHover',
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
// Action definitions
|
|
65
|
+
type Action = (
|
|
66
|
+
| {
|
|
67
|
+
// Action type
|
|
68
|
+
type: ActionType.StartHover,
|
|
69
|
+
// The ID of the option that the user is hovering over
|
|
70
|
+
hoveredOptionId: string,
|
|
71
|
+
}
|
|
72
|
+
| {
|
|
73
|
+
// Action type
|
|
74
|
+
type: (
|
|
75
|
+
| ActionType.StopHover
|
|
76
|
+
),
|
|
77
|
+
}
|
|
78
|
+
);
|
|
79
|
+
|
|
80
|
+
/**
|
|
81
|
+
* Reducer that executes actions
|
|
82
|
+
* @author Gabe Abrams
|
|
83
|
+
* @param state current state
|
|
84
|
+
* @param action action to execute
|
|
85
|
+
*/
|
|
86
|
+
const reducer = (state: State, action: Action): State => {
|
|
87
|
+
switch (action.type) {
|
|
88
|
+
case ActionType.StartHover: {
|
|
89
|
+
return {
|
|
90
|
+
...state,
|
|
91
|
+
hoveredOptionId: action.hoveredOptionId,
|
|
92
|
+
};
|
|
93
|
+
}
|
|
94
|
+
case ActionType.StopHover: {
|
|
95
|
+
return {
|
|
96
|
+
...state,
|
|
97
|
+
hoveredOptionId: undefined,
|
|
98
|
+
};
|
|
99
|
+
}
|
|
100
|
+
default: {
|
|
101
|
+
return state;
|
|
102
|
+
}
|
|
103
|
+
}
|
|
104
|
+
};
|
|
105
|
+
|
|
106
|
+
/*------------------------------------------------------------------------*/
|
|
107
|
+
/* ------------------------------ Component ----------------------------- */
|
|
108
|
+
/*------------------------------------------------------------------------*/
|
|
109
|
+
|
|
110
|
+
const MultiSwitch: React.FC<Props> = (props) => {
|
|
111
|
+
/*------------------------------------------------------------------------*/
|
|
112
|
+
/* -------------------------------- Setup ------------------------------- */
|
|
113
|
+
/*------------------------------------------------------------------------*/
|
|
114
|
+
|
|
115
|
+
/* -------------- Props ------------- */
|
|
116
|
+
|
|
117
|
+
// Destructure all props
|
|
118
|
+
const {
|
|
119
|
+
options,
|
|
120
|
+
selectedOptionId,
|
|
121
|
+
onChange,
|
|
122
|
+
heightRem = 3,
|
|
123
|
+
} = props;
|
|
124
|
+
|
|
125
|
+
/* -------------- State ------------- */
|
|
126
|
+
|
|
127
|
+
// Initial state
|
|
128
|
+
const initialState: State = {
|
|
129
|
+
hoveredOptionId: undefined,
|
|
130
|
+
};
|
|
131
|
+
|
|
132
|
+
// Initialize state
|
|
133
|
+
const [state, dispatch] = useReducer(reducer, initialState);
|
|
134
|
+
|
|
135
|
+
// Destructure common state
|
|
136
|
+
const {
|
|
137
|
+
hoveredOptionId,
|
|
138
|
+
} = state;
|
|
139
|
+
|
|
140
|
+
/*------------------------------------------------------------------------*/
|
|
141
|
+
/* -------------------------------- Style ------------------------------- */
|
|
142
|
+
/*------------------------------------------------------------------------*/
|
|
143
|
+
|
|
144
|
+
// Calculate the index of the selected option
|
|
145
|
+
const selectedOptionIndex = options.findIndex((option) => {
|
|
146
|
+
return (option.id === selectedOptionId);
|
|
147
|
+
});
|
|
148
|
+
|
|
149
|
+
// Constants
|
|
150
|
+
const gutterRems = heightRem * 0.1;
|
|
151
|
+
const itemWidthRems = heightRem * 1.3;
|
|
152
|
+
const textFontSize = heightRem / 3.5;
|
|
153
|
+
const iconFontSize = heightRem / 2;
|
|
154
|
+
const borderWidthRems = 0.05;
|
|
155
|
+
|
|
156
|
+
// Style
|
|
157
|
+
const style = `
|
|
158
|
+
.MultiSwitch-outer-box {
|
|
159
|
+
display: inline-block !important;
|
|
160
|
+
position: relative;
|
|
161
|
+
border-radius: 0.5rem !important;
|
|
162
|
+
border: ${borderWidthRems}rem solid #666 !important;
|
|
163
|
+
padding: 0 !important;
|
|
164
|
+
height: ${heightRem}rem !important;
|
|
165
|
+
width: ${gutterRems + (2 * borderWidthRems) + options.length * (itemWidthRems + gutterRems)}rem !important;
|
|
166
|
+
overflow: visible !important;
|
|
167
|
+
text-wrap: none !important;
|
|
168
|
+
z-index: 0;
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
.MultiSwitch-options-container {
|
|
172
|
+
position: relative;
|
|
173
|
+
display: flex;
|
|
174
|
+
flex-direction: row;
|
|
175
|
+
align-items: center;
|
|
176
|
+
justify-content: flex-start;
|
|
177
|
+
z-index: 1;
|
|
178
|
+
margin-top: ${gutterRems}rem;
|
|
179
|
+
width: ${gutterRems + options.length * (itemWidthRems + gutterRems)}rem;
|
|
180
|
+
overflow: visible !important;
|
|
181
|
+
text-wrap: none !important;
|
|
182
|
+
}
|
|
183
|
+
|
|
184
|
+
.MultiSwitch-icon {
|
|
185
|
+
font-size: ${iconFontSize}rems !important;
|
|
186
|
+
}
|
|
187
|
+
|
|
188
|
+
.MultiSwitch-option-button {
|
|
189
|
+
position: relative !important;
|
|
190
|
+
font-size: ${textFontSize}rem !important;
|
|
191
|
+
z-index: 1 !important;
|
|
192
|
+
display: inline-block !important;
|
|
193
|
+
flex-grow: 0 !important;
|
|
194
|
+
border: 0 !important;
|
|
195
|
+
border-radius: 0.5rem !important;
|
|
196
|
+
width: ${itemWidthRems}rem !important;
|
|
197
|
+
height: ${heightRem - (2 * gutterRems)}rem !important;
|
|
198
|
+
overflow: hidden;
|
|
199
|
+
text-overflow: ellipsis;
|
|
200
|
+
font-size: ${heightRem / 3}rem !important;
|
|
201
|
+
line-height: 1 !important;
|
|
202
|
+
margin-right: 0 !important;
|
|
203
|
+
margin-top: 0 !important;
|
|
204
|
+
margin-bottom: 0 !important;
|
|
205
|
+
margin-left: ${gutterRems}rem !important;
|
|
206
|
+
color: black !important;
|
|
207
|
+
background-color: transparent !important;
|
|
208
|
+
transition: background-color 0.3s ease-out !important;
|
|
209
|
+
}
|
|
210
|
+
|
|
211
|
+
.MultiSwitch-option-button-hovered {
|
|
212
|
+
background-color: #eee !important;
|
|
213
|
+
}
|
|
214
|
+
|
|
215
|
+
.MultiSwitch-option-text {
|
|
216
|
+
display: inline-block;
|
|
217
|
+
font-size: ${textFontSize}rem !important;
|
|
218
|
+
text-overflow: ellipsis;
|
|
219
|
+
white-space: nowrap;
|
|
220
|
+
width: 100%;
|
|
221
|
+
text-align: center !important;
|
|
222
|
+
}
|
|
223
|
+
|
|
224
|
+
.MultiSwitch-highlight-container {
|
|
225
|
+
position: absolute;
|
|
226
|
+
z-index: -1;
|
|
227
|
+
left: 0;
|
|
228
|
+
top: 0;
|
|
229
|
+
display: inline-block;
|
|
230
|
+
height: 0;
|
|
231
|
+
width: 0;
|
|
232
|
+
overflow: visible;
|
|
233
|
+
}
|
|
234
|
+
|
|
235
|
+
.MultiSwitch-highlight {
|
|
236
|
+
position: absolute;
|
|
237
|
+
border-radius: 0.5rem !important;
|
|
238
|
+
width: ${itemWidthRems}rem;
|
|
239
|
+
height: ${heightRem - (2 * gutterRems)}rem;
|
|
240
|
+
top: ${gutterRems}rem;
|
|
241
|
+
left: ${gutterRems}rem;
|
|
242
|
+
transform: translate(${selectedOptionIndex * (itemWidthRems + gutterRems) + borderWidthRems}rem, -${borderWidthRems * 0.5}rem);
|
|
243
|
+
transition: transform 0.3s ease-in-out;
|
|
244
|
+
}
|
|
245
|
+
`;
|
|
246
|
+
|
|
247
|
+
/*------------------------------------------------------------------------*/
|
|
248
|
+
/* ------------------------------- Render ------------------------------- */
|
|
249
|
+
/*------------------------------------------------------------------------*/
|
|
250
|
+
|
|
251
|
+
/*----------------------------------------*/
|
|
252
|
+
/* --------------- Main UI -------------- */
|
|
253
|
+
/*----------------------------------------*/
|
|
254
|
+
|
|
255
|
+
const optionElements = options.map((option) => {
|
|
256
|
+
return (
|
|
257
|
+
<button
|
|
258
|
+
type="button"
|
|
259
|
+
className={`MultiSwitch-option-button${option.id === hoveredOptionId ? ' MultiSwitch-option-button-hovered' : ''}`}
|
|
260
|
+
aria-label={(
|
|
261
|
+
option.id === selectedOptionId
|
|
262
|
+
? `option "${option.label}", currently selected`
|
|
263
|
+
: `click to select option "${option.label}"`
|
|
264
|
+
)}
|
|
265
|
+
onClick={() => {
|
|
266
|
+
// Remove hover
|
|
267
|
+
dispatch({
|
|
268
|
+
type: ActionType.StopHover,
|
|
269
|
+
});
|
|
270
|
+
|
|
271
|
+
// Notify parent
|
|
272
|
+
onChange(option.id);
|
|
273
|
+
}}
|
|
274
|
+
onMouseEnter={() => {
|
|
275
|
+
dispatch({
|
|
276
|
+
type: ActionType.StartHover,
|
|
277
|
+
hoveredOptionId: option.id,
|
|
278
|
+
});
|
|
279
|
+
}}
|
|
280
|
+
onMouseLeave={() => {
|
|
281
|
+
dispatch({
|
|
282
|
+
type: ActionType.StopHover,
|
|
283
|
+
});
|
|
284
|
+
}}
|
|
285
|
+
onFocus={() => {
|
|
286
|
+
dispatch({
|
|
287
|
+
type: ActionType.StartHover,
|
|
288
|
+
hoveredOptionId: option.id,
|
|
289
|
+
});
|
|
290
|
+
}}
|
|
291
|
+
onBlur={() => {
|
|
292
|
+
dispatch({
|
|
293
|
+
type: ActionType.StopHover,
|
|
294
|
+
});
|
|
295
|
+
}}
|
|
296
|
+
style={{
|
|
297
|
+
pointerEvents: (
|
|
298
|
+
(option.id === selectedOptionId)
|
|
299
|
+
? 'none'
|
|
300
|
+
: undefined
|
|
301
|
+
),
|
|
302
|
+
}}
|
|
303
|
+
disabled={option.id === selectedOptionId}
|
|
304
|
+
>
|
|
305
|
+
<div className="MultiSelect-icon">
|
|
306
|
+
<FontAwesomeIcon
|
|
307
|
+
icon={option.icon}
|
|
308
|
+
/>
|
|
309
|
+
</div>
|
|
310
|
+
<div className="MultiSwitch-option-text">
|
|
311
|
+
{option.label}
|
|
312
|
+
</div>
|
|
313
|
+
</button>
|
|
314
|
+
);
|
|
315
|
+
});
|
|
316
|
+
|
|
317
|
+
// Highlight behind selected option
|
|
318
|
+
const highlight = (
|
|
319
|
+
<div className="MultiSwitch-highlight bg-warning d-inline-block" />
|
|
320
|
+
);
|
|
321
|
+
|
|
322
|
+
return (
|
|
323
|
+
<div className="MultiSwitch-outer-box alert alert-dark">
|
|
324
|
+
{/* Style */}
|
|
325
|
+
<style>
|
|
326
|
+
{style}
|
|
327
|
+
</style>
|
|
328
|
+
|
|
329
|
+
{/* Options */}
|
|
330
|
+
<div className="MultiSwitch-options-container">
|
|
331
|
+
{optionElements}
|
|
332
|
+
</div>
|
|
333
|
+
|
|
334
|
+
{/* Highlight */}
|
|
335
|
+
<div className="MultiSwitch-highlight-container">
|
|
336
|
+
{highlight}
|
|
337
|
+
</div>
|
|
338
|
+
</div>
|
|
339
|
+
);
|
|
340
|
+
};
|
|
341
|
+
|
|
342
|
+
/*------------------------------------------------------------------------*/
|
|
343
|
+
/* ------------------------------- Wrap Up ------------------------------ */
|
|
344
|
+
/*------------------------------------------------------------------------*/
|
|
345
|
+
|
|
346
|
+
// Export component
|
|
347
|
+
export default MultiSwitch;
|
|
@@ -27,6 +27,8 @@ type Props = {
|
|
|
27
27
|
wide?: boolean,
|
|
28
28
|
// If true, tooltip is thinner
|
|
29
29
|
thin?: boolean,
|
|
30
|
+
// If true, the tooltip container should be display: block
|
|
31
|
+
containerIsDisplayBlock?: boolean,
|
|
30
32
|
};
|
|
31
33
|
|
|
32
34
|
/*------------------------------------------------------------------------*/
|
|
@@ -48,7 +50,6 @@ const TOOLTIP_BORDER_RADIUS_REM = 0.3;
|
|
|
48
50
|
const style = `
|
|
49
51
|
/* Container for contents and tooltip */
|
|
50
52
|
.Tooltip-outer-container {
|
|
51
|
-
display: inline-block;
|
|
52
53
|
position: relative;
|
|
53
54
|
}
|
|
54
55
|
|
|
@@ -339,6 +340,7 @@ const Tooltip: React.FC<Props> = (props) => {
|
|
|
339
340
|
children,
|
|
340
341
|
wide,
|
|
341
342
|
thin,
|
|
343
|
+
containerIsDisplayBlock,
|
|
342
344
|
} = props;
|
|
343
345
|
|
|
344
346
|
/* -------------- State ------------- */
|
|
@@ -463,7 +465,7 @@ const Tooltip: React.FC<Props> = (props) => {
|
|
|
463
465
|
// Render
|
|
464
466
|
return (
|
|
465
467
|
<div
|
|
466
|
-
className=
|
|
468
|
+
className={`Tooltip-outer-container ${containerIsDisplayBlock ? 'd-block' : 'd-inline-block'}}`}
|
|
467
469
|
aria-label={text}
|
|
468
470
|
onMouseEnter={() => {
|
|
469
471
|
dispatch({
|
package/src/index.ts
CHANGED
|
@@ -27,6 +27,7 @@ import DBEntryManagerPanel from './components/DBEntryManagerPanel';
|
|
|
27
27
|
import Tooltip from './components/Tooltip';
|
|
28
28
|
import ToggleSwitch from './components/ToggleSwitch';
|
|
29
29
|
import AutoscrollToBottomContainer from './components/AutoscrollToBottomContainer';
|
|
30
|
+
import MultiSwitch from './components/MultiSwitch';
|
|
30
31
|
|
|
31
32
|
// Import errors
|
|
32
33
|
import ErrorWithCode from './errors/ErrorWithCode';
|
|
@@ -129,6 +130,7 @@ export {
|
|
|
129
130
|
Tooltip,
|
|
130
131
|
ToggleSwitch,
|
|
131
132
|
AutoscrollToBottomContainer,
|
|
133
|
+
MultiSwitch,
|
|
132
134
|
// Global functions
|
|
133
135
|
alert,
|
|
134
136
|
confirm,
|