dce-reactkit 3.6.8 → 3.6.10

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;
@@ -0,0 +1,27 @@
1
+ /**
2
+ * Merges a list of class names into a class name, intelligently handling spaces
3
+ * @author Gabe Abrams
4
+ * @param classNames the list of class names to merge
5
+ * @returns the merged class name
6
+ */
7
+ const combineClassNames = (classNames: string[]): string => {
8
+ return (
9
+ classNames
10
+ // Trim whitespace
11
+ .map((className) => {
12
+ return className.trim();
13
+ })
14
+ // Remove empty class names
15
+ .filter((className) => {
16
+ return className.length > 0;
17
+ })
18
+ // Change multiple spaces for just one space
19
+ .map((className) => {
20
+ return className.replace(/\s+/g, ' ');
21
+ })
22
+ // Join with spaces
23
+ .join(' ')
24
+ );
25
+ };
26
+
27
+ export default combineClassNames;
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';
@@ -81,6 +82,7 @@ import validatePhoneNumber from './helpers/validators/validatePhoneNumber';
81
82
  import validateString from './helpers/validators/validateString';
82
83
  import idify from './helpers/idify';
83
84
  import makeLinksClickable from './helpers/makeLinksClickable';
85
+ import combineClassNames from './helpers/combineClassNames';
84
86
 
85
87
  // Import types
86
88
  import ModalButtonType from './types/ModalButtonType';
@@ -129,6 +131,7 @@ export {
129
131
  Tooltip,
130
132
  ToggleSwitch,
131
133
  AutoscrollToBottomContainer,
134
+ MultiSwitch,
132
135
  // Global functions
133
136
  alert,
134
137
  confirm,
@@ -180,6 +183,7 @@ export {
180
183
  logClientEvent,
181
184
  addFatalErrorHandler,
182
185
  leaveToURL,
186
+ combineClassNames,
183
187
  // Server helpers
184
188
  initServer,
185
189
  genRouteHandler,