dce-reactkit 3.6.8 → 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.
@@ -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;
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,