@oneuptime/common 8.0.5571 → 8.0.5573

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.
Files changed (79) hide show
  1. package/Models/DatabaseModels/AlertFeed.ts +2 -0
  2. package/Models/DatabaseModels/AlertSeverity.ts +2 -0
  3. package/Models/DatabaseModels/AlertState.ts +2 -0
  4. package/Models/DatabaseModels/DatabaseBaseModel/DatabaseBaseModel.ts +5 -0
  5. package/Models/DatabaseModels/IncidentSeverity.ts +2 -0
  6. package/Models/DatabaseModels/IncidentState.ts +2 -0
  7. package/Models/DatabaseModels/Label.ts +2 -0
  8. package/Models/DatabaseModels/MonitorStatus.ts +2 -0
  9. package/Models/DatabaseModels/ScheduledMaintenanceFeed.ts +2 -0
  10. package/Models/DatabaseModels/ScheduledMaintenanceState.ts +2 -0
  11. package/Models/DatabaseModels/ServiceCatalog.ts +2 -0
  12. package/Models/DatabaseModels/TelemetryService.ts +2 -0
  13. package/Server/Services/TeamMemberService.ts +6 -1
  14. package/Tests/UI/Components/Pill.test.tsx +8 -0
  15. package/Types/Color.ts +2 -2
  16. package/Types/Database/ColorField.ts +64 -0
  17. package/Types/Icon/IconProp.ts +1 -0
  18. package/UI/Components/Dropdown/Dropdown.tsx +503 -10
  19. package/UI/Components/Forms/ModelForm.tsx +63 -26
  20. package/UI/Components/Icon/Icon.tsx +4 -0
  21. package/UI/Components/Label/Label.tsx +50 -0
  22. package/UI/Components/Label/Labels.tsx +35 -0
  23. package/UI/Components/ModelTable/BaseModelTable.tsx +134 -6
  24. package/UI/Components/Page/Page.tsx +7 -23
  25. package/UI/Components/Pill/Pill.tsx +13 -3
  26. package/UI/Utils/Dropdown.ts +19 -1
  27. package/build/dist/Models/DatabaseModels/AlertFeed.js +2 -0
  28. package/build/dist/Models/DatabaseModels/AlertFeed.js.map +1 -1
  29. package/build/dist/Models/DatabaseModels/AlertSeverity.js +2 -0
  30. package/build/dist/Models/DatabaseModels/AlertSeverity.js.map +1 -1
  31. package/build/dist/Models/DatabaseModels/AlertState.js +2 -0
  32. package/build/dist/Models/DatabaseModels/AlertState.js.map +1 -1
  33. package/build/dist/Models/DatabaseModels/DatabaseBaseModel/DatabaseBaseModel.js +4 -0
  34. package/build/dist/Models/DatabaseModels/DatabaseBaseModel/DatabaseBaseModel.js.map +1 -1
  35. package/build/dist/Models/DatabaseModels/IncidentSeverity.js +2 -0
  36. package/build/dist/Models/DatabaseModels/IncidentSeverity.js.map +1 -1
  37. package/build/dist/Models/DatabaseModels/IncidentState.js +2 -0
  38. package/build/dist/Models/DatabaseModels/IncidentState.js.map +1 -1
  39. package/build/dist/Models/DatabaseModels/Label.js +2 -0
  40. package/build/dist/Models/DatabaseModels/Label.js.map +1 -1
  41. package/build/dist/Models/DatabaseModels/MonitorStatus.js +2 -0
  42. package/build/dist/Models/DatabaseModels/MonitorStatus.js.map +1 -1
  43. package/build/dist/Models/DatabaseModels/ScheduledMaintenanceFeed.js +2 -0
  44. package/build/dist/Models/DatabaseModels/ScheduledMaintenanceFeed.js.map +1 -1
  45. package/build/dist/Models/DatabaseModels/ScheduledMaintenanceState.js +2 -0
  46. package/build/dist/Models/DatabaseModels/ScheduledMaintenanceState.js.map +1 -1
  47. package/build/dist/Models/DatabaseModels/ServiceCatalog.js +2 -0
  48. package/build/dist/Models/DatabaseModels/ServiceCatalog.js.map +1 -1
  49. package/build/dist/Models/DatabaseModels/TelemetryService.js +2 -0
  50. package/build/dist/Models/DatabaseModels/TelemetryService.js.map +1 -1
  51. package/build/dist/Server/Services/TeamMemberService.js +3 -1
  52. package/build/dist/Server/Services/TeamMemberService.js.map +1 -1
  53. package/build/dist/Tests/UI/Components/Pill.test.js +6 -0
  54. package/build/dist/Tests/UI/Components/Pill.test.js.map +1 -1
  55. package/build/dist/Types/Color.js +1 -1
  56. package/build/dist/Types/Color.js.map +1 -1
  57. package/build/dist/Types/Database/ColorField.js +27 -0
  58. package/build/dist/Types/Database/ColorField.js.map +1 -0
  59. package/build/dist/Types/Icon/IconProp.js +1 -0
  60. package/build/dist/Types/Icon/IconProp.js.map +1 -1
  61. package/build/dist/UI/Components/Dropdown/Dropdown.js +264 -8
  62. package/build/dist/UI/Components/Dropdown/Dropdown.js.map +1 -1
  63. package/build/dist/UI/Components/Forms/ModelForm.js +33 -10
  64. package/build/dist/UI/Components/Forms/ModelForm.js.map +1 -1
  65. package/build/dist/UI/Components/Icon/Icon.js +5 -0
  66. package/build/dist/UI/Components/Icon/Icon.js.map +1 -1
  67. package/build/dist/UI/Components/Label/Label.js +32 -0
  68. package/build/dist/UI/Components/Label/Label.js.map +1 -0
  69. package/build/dist/UI/Components/Label/Labels.js +13 -0
  70. package/build/dist/UI/Components/Label/Labels.js.map +1 -0
  71. package/build/dist/UI/Components/ModelTable/BaseModelTable.js +82 -6
  72. package/build/dist/UI/Components/ModelTable/BaseModelTable.js.map +1 -1
  73. package/build/dist/UI/Components/Page/Page.js +3 -14
  74. package/build/dist/UI/Components/Page/Page.js.map +1 -1
  75. package/build/dist/UI/Components/Pill/Pill.js +4 -4
  76. package/build/dist/UI/Components/Pill/Pill.js.map +1 -1
  77. package/build/dist/UI/Utils/Dropdown.js +11 -1
  78. package/build/dist/UI/Utils/Dropdown.js.map +1 -1
  79. package/package.json +1 -1
@@ -6,13 +6,32 @@ import React, {
6
6
  useRef,
7
7
  useState,
8
8
  } from "react";
9
- import Select, { ControlProps, GroupBase, OptionProps } from "react-select";
9
+ import Color from "../../../Types/Color";
10
+ import Label from "../../../Models/DatabaseModels/Label";
11
+ import Select, {
12
+ ControlProps,
13
+ CSSObjectWithLabel,
14
+ FormatOptionLabelMeta,
15
+ GroupBase,
16
+ OptionProps,
17
+ } from "react-select";
10
18
 
11
19
  export type DropdownValue = string | number | boolean;
12
20
 
21
+ export type DropdownOptionLabel =
22
+ | Label
23
+ | {
24
+ id?: string;
25
+ name: string;
26
+ color?: Color;
27
+ };
28
+
13
29
  export interface DropdownOption {
14
30
  value: DropdownValue;
15
31
  label: string;
32
+ description?: string;
33
+ labels?: Array<DropdownOptionLabel>;
34
+ color?: Color;
16
35
  }
17
36
 
18
37
  export interface ComponentProps {
@@ -111,6 +130,333 @@ const Dropdown: FunctionComponent<ComponentProps> = (
111
130
 
112
131
  const firstUpdate: React.MutableRefObject<boolean> = useRef(true);
113
132
 
133
+ interface NormalizedDropdownLabel {
134
+ id?: string;
135
+ name: string;
136
+ color?: string;
137
+ }
138
+
139
+ const normalizeLabelColor: (
140
+ color?: Color | string | null,
141
+ ) => string | undefined = (
142
+ color?: Color | string | null,
143
+ ): string | undefined => {
144
+ if (!color) {
145
+ return undefined;
146
+ }
147
+
148
+ if (color instanceof Color) {
149
+ return color.toString();
150
+ }
151
+
152
+ if (typeof color === "string" && color.trim().length > 0) {
153
+ return color;
154
+ }
155
+
156
+ return undefined;
157
+ };
158
+
159
+ const normalizeDropdownLabel: (
160
+ label: DropdownOptionLabel,
161
+ ) => NormalizedDropdownLabel | null = (
162
+ label: DropdownOptionLabel,
163
+ ): NormalizedDropdownLabel | null => {
164
+ if (!label) {
165
+ return null;
166
+ }
167
+
168
+ const getValueFromModel: (
169
+ columnName: string,
170
+ ) => string | Color | null | undefined = (
171
+ columnName: string,
172
+ ): string | Color | null | undefined => {
173
+ if (
174
+ typeof (label as Label).getColumnValue === "function" &&
175
+ typeof (label as Label).getTableColumnMetadata === "function"
176
+ ) {
177
+ return (label as Label).getColumnValue(columnName) as
178
+ | string
179
+ | Color
180
+ | null
181
+ | undefined;
182
+ }
183
+
184
+ return (label as any)?.[columnName] as string | Color | null | undefined;
185
+ };
186
+
187
+ const labelName: string | undefined = (() => {
188
+ const valueFromGetter: string | null | undefined = getValueFromModel(
189
+ "name",
190
+ ) as string | undefined | null;
191
+
192
+ if (valueFromGetter && valueFromGetter.trim().length > 0) {
193
+ return valueFromGetter;
194
+ }
195
+
196
+ const fallbackName: string | undefined = (label as any)?.name;
197
+ if (fallbackName && fallbackName.trim().length > 0) {
198
+ return fallbackName;
199
+ }
200
+
201
+ return undefined;
202
+ })();
203
+
204
+ if (!labelName) {
205
+ return null;
206
+ }
207
+
208
+ const rawColor: Color | string | null | undefined = getValueFromModel(
209
+ "color",
210
+ ) as Color | string | null | undefined;
211
+ const color: string | undefined =
212
+ normalizeLabelColor(rawColor) ||
213
+ normalizeLabelColor((label as any)?.color);
214
+
215
+ const idValue: string | undefined = (() => {
216
+ if (typeof (label as Label).id !== "undefined") {
217
+ const idFromGetter: ObjectID | null | undefined = (label as Label).id;
218
+ if (idFromGetter) {
219
+ return idFromGetter.toString();
220
+ }
221
+ }
222
+
223
+ const fallbackId: string | undefined =
224
+ (label as any)?._id || (label as any)?.id;
225
+
226
+ if (fallbackId) {
227
+ return fallbackId;
228
+ }
229
+
230
+ return undefined;
231
+ })();
232
+
233
+ const normalized: NormalizedDropdownLabel = {
234
+ name: labelName,
235
+ };
236
+
237
+ if (idValue) {
238
+ normalized.id = idValue;
239
+ }
240
+
241
+ if (color) {
242
+ normalized.color = color;
243
+ }
244
+
245
+ return normalized;
246
+ };
247
+
248
+ const normalizeLabelCollection: (
249
+ labels?: Array<DropdownOptionLabel>,
250
+ ) => Array<NormalizedDropdownLabel> = (
251
+ labels?: Array<DropdownOptionLabel>,
252
+ ): Array<NormalizedDropdownLabel> => {
253
+ if (!labels || labels.length === 0) {
254
+ return [];
255
+ }
256
+
257
+ return labels
258
+ .map((label: DropdownOptionLabel) => {
259
+ return normalizeDropdownLabel(label);
260
+ })
261
+ .filter(
262
+ (
263
+ label: NormalizedDropdownLabel | null,
264
+ ): label is NormalizedDropdownLabel => {
265
+ return label !== null;
266
+ },
267
+ );
268
+ };
269
+
270
+ const renderOptionColorIndicator: (
271
+ color?: Color | string,
272
+ ) => ReactElement | null = (color?: Color | string): ReactElement | null => {
273
+ const normalizedColor: string | undefined = color
274
+ ? new Color(color).toString()
275
+ : undefined;
276
+
277
+ if (!normalizedColor) {
278
+ return null;
279
+ }
280
+
281
+ return (
282
+ <span
283
+ aria-hidden="true"
284
+ className="h-2.5 w-2.5 flex-none rounded-full border border-gray-200"
285
+ style={{
286
+ backgroundColor: normalizedColor,
287
+ }}
288
+ title={normalizedColor}
289
+ ></span>
290
+ );
291
+ };
292
+
293
+ const getLabelStyle: (color?: string) => {
294
+ backgroundColor: string;
295
+ color: string;
296
+ } = (color?: string): { backgroundColor: string; color: string } => {
297
+ if (!color) {
298
+ return {
299
+ backgroundColor: "#e5e7eb", // gray-200
300
+ color: "#374151", // gray-700
301
+ };
302
+ }
303
+
304
+ try {
305
+ const parsedColor: Color = Color.fromString(color);
306
+ return {
307
+ backgroundColor: parsedColor.toString(),
308
+ color: Color.shouldUseDarkText(parsedColor)
309
+ ? "#111827" // gray-900
310
+ : "#f9fafb", // gray-50
311
+ };
312
+ } catch {
313
+ return {
314
+ backgroundColor: color,
315
+ color: "#111827",
316
+ };
317
+ }
318
+ };
319
+
320
+ const defaultSelectedLabelAccentColor: string = "#6366f1"; // indigo-500
321
+
322
+ const resolveSelectedLabelColor: (color?: string) => string = (
323
+ color?: string,
324
+ ): string => {
325
+ if (!color) {
326
+ return defaultSelectedLabelAccentColor;
327
+ }
328
+
329
+ try {
330
+ return Color.fromString(color).toString();
331
+ } catch {
332
+ return defaultSelectedLabelAccentColor;
333
+ }
334
+ };
335
+
336
+ const renderAssociatedLabels: (
337
+ labels: Array<NormalizedDropdownLabel>,
338
+ context: FormatOptionLabelMeta<DropdownOption>["context"],
339
+ hiddenLabelCount: number,
340
+ ) => ReactElement | null = (
341
+ labels: Array<NormalizedDropdownLabel>,
342
+ context: FormatOptionLabelMeta<DropdownOption>["context"],
343
+ hiddenLabelCount: number,
344
+ ): ReactElement | null => {
345
+ if (labels.length === 0 && hiddenLabelCount === 0) {
346
+ return null;
347
+ }
348
+
349
+ if (context === "value") {
350
+ return (
351
+ <div className="flex flex-wrap items-center gap-1">
352
+ {labels.map((label: NormalizedDropdownLabel, index: number) => {
353
+ const accentColor: string = resolveSelectedLabelColor(label.color);
354
+
355
+ return (
356
+ <span
357
+ key={`${label.id || label.name}-selected-${index}`}
358
+ className="inline-flex items-center gap-1 rounded-full border border-gray-200 bg-white px-2 py-0.5 text-xs font-medium text-gray-600"
359
+ style={{
360
+ borderColor: accentColor,
361
+ }}
362
+ title={label.name}
363
+ >
364
+ <span
365
+ aria-hidden="true"
366
+ className="h-1.5 w-1.5 rounded-full"
367
+ style={{
368
+ backgroundColor: accentColor,
369
+ }}
370
+ ></span>
371
+ {label.name}
372
+ </span>
373
+ );
374
+ })}
375
+ {hiddenLabelCount > 0 ? (
376
+ <span className="inline-flex items-center rounded-full border border-gray-200 bg-white px-2 py-0.5 text-xs font-medium text-gray-500">
377
+ +{hiddenLabelCount}
378
+ </span>
379
+ ) : null}
380
+ </div>
381
+ );
382
+ }
383
+
384
+ return (
385
+ <div className="flex flex-wrap gap-1">
386
+ {labels.map((label: NormalizedDropdownLabel, index: number) => {
387
+ const { backgroundColor, color } = getLabelStyle(label.color);
388
+
389
+ return (
390
+ <span
391
+ key={`${label.id || label.name}-menu-${index}`}
392
+ className="inline-flex items-center rounded-full px-2 py-0.5 text-xs font-medium shadow-sm"
393
+ style={{ backgroundColor, color }}
394
+ >
395
+ {label.name}
396
+ </span>
397
+ );
398
+ })}
399
+ {hiddenLabelCount > 0 ? (
400
+ <span className="inline-flex items-center rounded-full bg-gray-100 px-2 py-0.5 text-xs font-medium text-gray-600">
401
+ +{hiddenLabelCount}
402
+ </span>
403
+ ) : null}
404
+ </div>
405
+ );
406
+ };
407
+
408
+ const formatDropdownOptionLabel: (
409
+ option: DropdownOption,
410
+ meta: FormatOptionLabelMeta<DropdownOption>,
411
+ ) => ReactElement = (
412
+ option: DropdownOption,
413
+ meta: FormatOptionLabelMeta<DropdownOption>,
414
+ ): ReactElement => {
415
+ const normalizedLabels: Array<NormalizedDropdownLabel> =
416
+ normalizeLabelCollection(option.labels);
417
+
418
+ const maxVisibleLabels: number = meta.context === "menu" ? 4 : 2;
419
+ const visibleLabels: Array<NormalizedDropdownLabel> =
420
+ normalizedLabels.slice(0, maxVisibleLabels);
421
+ const hiddenLabelCount: number = Math.max(
422
+ normalizedLabels.length - visibleLabels.length,
423
+ 0,
424
+ );
425
+
426
+ if (meta.context === "value") {
427
+ return (
428
+ <div className="flex flex-wrap items-center gap-2">
429
+ <div className="flex items-center gap-2">
430
+ {renderOptionColorIndicator(option.color)}
431
+ <span className="text-sm font-medium text-gray-900">
432
+ {option.label}
433
+ </span>
434
+ </div>
435
+ {renderAssociatedLabels(
436
+ visibleLabels,
437
+ meta.context,
438
+ hiddenLabelCount,
439
+ )}
440
+ </div>
441
+ );
442
+ }
443
+
444
+ return (
445
+ <div className="flex flex-col gap-1">
446
+ <div className="flex items-center gap-2">
447
+ {renderOptionColorIndicator(option.color)}
448
+ <span className="text-sm font-medium text-gray-900">
449
+ {option.label}
450
+ </span>
451
+ </div>
452
+ {option.description ? (
453
+ <span className="text-xs text-gray-500">{option.description}</span>
454
+ ) : null}
455
+ {renderAssociatedLabels(visibleLabels, meta.context, hiddenLabelCount)}
456
+ </div>
457
+ );
458
+ };
459
+
114
460
  useLayoutEffect(() => {
115
461
  if (firstUpdate.current && props.initialValue) {
116
462
  firstUpdate.current = false;
@@ -138,6 +484,9 @@ const Dropdown: FunctionComponent<ComponentProps> = (
138
484
  }}
139
485
  >
140
486
  <Select
487
+ classNamePrefix="ou-select"
488
+ unstyled={false}
489
+ formatOptionLabel={formatDropdownOptionLabel}
141
490
  onBlur={() => {
142
491
  props.onBlur?.();
143
492
  }}
@@ -150,25 +499,169 @@ const Dropdown: FunctionComponent<ComponentProps> = (
150
499
  }}
151
500
  classNames={{
152
501
  control: (
153
- state: ControlProps<any, boolean, GroupBase<any>>,
502
+ state: ControlProps<DropdownOption, boolean, GroupBase<any>>,
154
503
  ): string => {
155
- return state.isFocused
156
- ? "!border-indigo-500"
157
- : "border-Gray500-300";
504
+ const classes: Array<string> = [
505
+ "!min-h-[40px] !rounded-lg !border !bg-white !shadow-sm !transition-all !duration-150",
506
+ state.isFocused
507
+ ? "!border-indigo-400 !ring-2 !ring-indigo-100"
508
+ : "!border-gray-300 hover:!border-indigo-300",
509
+ state.isDisabled
510
+ ? "!bg-gray-100 !text-gray-400"
511
+ : "!cursor-pointer",
512
+ ];
513
+
514
+ if (props.error) {
515
+ classes.push("!border-red-400 !ring-2 !ring-red-100");
516
+ }
517
+
518
+ return classes.join(" ");
519
+ },
520
+ valueContainer: () => {
521
+ return "!gap-2 !px-2";
522
+ },
523
+ placeholder: () => {
524
+ return "text-sm text-gray-400";
525
+ },
526
+ input: () => {
527
+ return "text-sm text-gray-900";
528
+ },
529
+ singleValue: () => {
530
+ return "text-sm text-gray-900 font-medium";
531
+ },
532
+ indicatorsContainer: () => {
533
+ return "!gap-1 !px-1";
534
+ },
535
+ dropdownIndicator: () => {
536
+ return "text-gray-500 transition-colors duration-150 hover:text-indigo-400";
537
+ },
538
+ clearIndicator: () => {
539
+ return "text-gray-400 transition-colors duration-150 hover:text-red-500";
540
+ },
541
+ menu: () => {
542
+ return "!mt-2 !rounded-xl !border !border-gray-100 !bg-white !shadow-xl";
543
+ },
544
+ menuList: () => {
545
+ return "!py-2";
158
546
  },
159
547
  option: (
160
- state: OptionProps<any, boolean, GroupBase<any>>,
548
+ state: OptionProps<DropdownOption, boolean, GroupBase<any>>,
161
549
  ): string => {
162
550
  if (state.isDisabled) {
163
- return "bg-gray-100";
551
+ return "px-3 py-2 text-sm text-gray-300 cursor-not-allowed";
552
+ }
553
+
554
+ if (state.isSelected) {
555
+ return "px-3 py-2 text-sm bg-indigo-200 text-indigo-900";
556
+ }
557
+
558
+ if (state.isFocused) {
559
+ return "px-3 py-2 text-sm bg-indigo-100 text-indigo-700";
164
560
  }
561
+
562
+ return "px-3 py-2 text-sm text-gray-700";
563
+ },
564
+ noOptionsMessage: () => {
565
+ return "px-3 py-2 text-sm text-gray-500";
566
+ },
567
+ multiValue: () => {
568
+ return "flex items-center gap-2 rounded-lg border border-indigo-100 bg-indigo-50 px-2 py-1";
569
+ },
570
+ multiValueLabel: () => {
571
+ return "flex flex-wrap items-center gap-2 text-sm font-medium text-indigo-900";
572
+ },
573
+ multiValueRemove: () => {
574
+ return "text-indigo-400 hover:text-indigo-600 transition-colors duration-150";
575
+ },
576
+ }}
577
+ styles={{
578
+ dropdownIndicator: (
579
+ provided: CSSObjectWithLabel,
580
+ ): CSSObjectWithLabel => {
581
+ return {
582
+ ...provided,
583
+ padding: 8,
584
+ };
585
+ },
586
+ clearIndicator: (
587
+ provided: CSSObjectWithLabel,
588
+ ): CSSObjectWithLabel => {
589
+ return {
590
+ ...provided,
591
+ padding: 8,
592
+ };
593
+ },
594
+ indicatorSeparator: (): CSSObjectWithLabel => {
595
+ return {
596
+ display: "none",
597
+ } as CSSObjectWithLabel;
598
+ },
599
+ option: (
600
+ provided: CSSObjectWithLabel,
601
+ state: OptionProps<
602
+ DropdownOption,
603
+ boolean,
604
+ GroupBase<DropdownOption>
605
+ >,
606
+ ): CSSObjectWithLabel => {
165
607
  if (state.isSelected) {
166
- return "!bg-indigo-500";
608
+ return {
609
+ ...provided,
610
+ backgroundColor: "#c7d2fe", // indigo-200
611
+ color: "#1e1b4b", // indigo-900
612
+ };
167
613
  }
614
+
168
615
  if (state.isFocused) {
169
- return "!bg-indigo-100";
616
+ return {
617
+ ...provided,
618
+ backgroundColor: "#e0e7ff", // indigo-100
619
+ color: "#312e81", // indigo-800
620
+ };
170
621
  }
171
- return "";
622
+
623
+ return {
624
+ ...provided,
625
+ color: "#374151", // gray-700
626
+ };
627
+ },
628
+ multiValue: (provided: CSSObjectWithLabel): CSSObjectWithLabel => {
629
+ return {
630
+ ...provided,
631
+ backgroundColor: "#eef2ff", // indigo-50
632
+ borderRadius: 8,
633
+ border: "1px solid #c7d2fe", // indigo-200
634
+ paddingLeft: 4,
635
+ paddingRight: 4,
636
+ };
637
+ },
638
+ multiValueLabel: (
639
+ provided: CSSObjectWithLabel,
640
+ ): CSSObjectWithLabel => {
641
+ return {
642
+ ...provided,
643
+ color: "#312e81", // indigo-800
644
+ fontSize: "0.875rem",
645
+ fontWeight: 500,
646
+ };
647
+ },
648
+ multiValueRemove: (
649
+ provided: CSSObjectWithLabel,
650
+ ): CSSObjectWithLabel => {
651
+ return {
652
+ ...provided,
653
+ color: "#6366f1", // indigo-500
654
+ ":hover": {
655
+ color: "#4f46e5", // indigo-600
656
+ backgroundColor: "transparent",
657
+ },
658
+ };
659
+ },
660
+ menuPortal: (base: CSSObjectWithLabel): CSSObjectWithLabel => {
661
+ return {
662
+ ...base,
663
+ zIndex: 50,
664
+ };
172
665
  },
173
666
  }}
174
667
  isClearable={true}
@@ -12,6 +12,7 @@ import {
12
12
  CategoryCheckboxOption,
13
13
  CheckboxCategory,
14
14
  } from "../CategoryCheckbox/CategoryCheckboxTypes";
15
+ import type { DropdownOption } from "../Dropdown/Dropdown";
15
16
  import Loader, { LoaderType } from "../Loader/Loader";
16
17
  import Pill, { PillSize } from "../Pill/Pill";
17
18
  import { FormErrors, FormProps, FormSummaryConfig } from "./BasicForm";
@@ -24,6 +25,7 @@ import AnalyticsBaseModel from "../../../Models/AnalyticsModels/AnalyticsBaseMod
24
25
  import AccessControlModel from "../../../Models/DatabaseModels/DatabaseBaseModel/AccessControlModel";
25
26
  import BaseModel from "../../../Models/DatabaseModels/DatabaseBaseModel/DatabaseBaseModel";
26
27
  import FileModel from "../../../Models/DatabaseModels/DatabaseBaseModel/FileModel";
28
+ import Label from "../../../Models/DatabaseModels/Label";
27
29
  import URL from "../../../Types/API/URL";
28
30
  import { ColumnAccessControl } from "../../../Types/BaseDatabase/AccessControl";
29
31
  import { Black, VeryLightGray } from "../../../Types/BrandColors";
@@ -412,17 +414,26 @@ const ModelForm: <TBaseModel extends BaseModel>(
412
414
  [field.dropdownModal.valueField]: true,
413
415
  } as any;
414
416
 
415
- let hasAccessControlColumn: boolean = false;
417
+ let colorColumnName: string | null = null;
418
+ let shouldSelectColorColumn: boolean = false;
419
+
420
+ colorColumnName = tempModel.getFirstColorColumn();
421
+
422
+ if (colorColumnName) {
423
+ select[colorColumnName] = true;
424
+ shouldSelectColorColumn = true;
425
+ }
426
+
427
+ const accessControlColumnName: string | null =
428
+ tempModel.getAccessControlColumn();
416
429
 
417
430
  // also select labels, so they can select resources by labels. This is useful for resources like monitors, etc.
418
- if (tempModel.getAccessControlColumn()) {
419
- select[tempModel.getAccessControlColumn()!] = {
431
+ if (accessControlColumnName) {
432
+ select[accessControlColumnName] = {
420
433
  _id: true,
421
434
  name: true,
422
435
  color: true,
423
436
  } as any;
424
-
425
- hasAccessControlColumn = true;
426
437
  }
427
438
 
428
439
  const listResult: ListResult<BaseModel> =
@@ -436,22 +447,51 @@ const ModelForm: <TBaseModel extends BaseModel>(
436
447
  });
437
448
 
438
449
  if (listResult.data && listResult.data.length > 0) {
439
- field.dropdownOptions = listResult.data.map((item: BaseModel) => {
440
- if (!field.dropdownModal) {
441
- throw new BadDataException("Dropdown Modal value mot found");
442
- }
450
+ const dropdownOptions: Array<DropdownOption> = listResult.data.map(
451
+ (item: BaseModel) => {
452
+ if (!field.dropdownModal) {
453
+ throw new BadDataException("Dropdown Modal value mot found");
454
+ }
443
455
 
444
- return {
445
- label: (item as any)[
446
- field.dropdownModal?.labelField
447
- ].toString(),
448
- value: (item as any)[
449
- field.dropdownModal?.valueField
450
- ].toString(),
451
- };
452
- });
456
+ const option: DropdownOption = {
457
+ label: (item as any)[
458
+ field.dropdownModal?.labelField
459
+ ].toString(),
460
+ value: (item as any)[
461
+ field.dropdownModal?.valueField
462
+ ].toString(),
463
+ };
464
+
465
+ if (colorColumnName && shouldSelectColorColumn) {
466
+ const color: Color = item.getColumnValue(
467
+ colorColumnName,
468
+ ) as Color;
469
+ if (color) {
470
+ option.color = color;
471
+ }
472
+ }
473
+
474
+ if (accessControlColumnName) {
475
+ const labelsForItem: Array<AccessControlModel> = (
476
+ ((item as any)[
477
+ accessControlColumnName
478
+ ] as Array<AccessControlModel>) || []
479
+ ).filter((label: AccessControlModel | null) => {
480
+ return Boolean(label);
481
+ }) as Array<AccessControlModel>;
482
+
483
+ if (labelsForItem.length > 0) {
484
+ option.labels = labelsForItem as Array<Label>;
485
+ }
486
+ }
487
+
488
+ return option;
489
+ },
490
+ );
491
+
492
+ field.dropdownOptions = dropdownOptions;
453
493
 
454
- if (hasAccessControlColumn) {
494
+ if (accessControlColumnName) {
455
495
  const categories: Array<CheckboxCategory> = [];
456
496
 
457
497
  // populate categories.
@@ -459,8 +499,7 @@ const ModelForm: <TBaseModel extends BaseModel>(
459
499
  let localLabels: Array<AccessControlModel> = [];
460
500
 
461
501
  for (const item of listResult.data) {
462
- const accessControlColumn: string | null =
463
- tempModel.getAccessControlColumn()!;
502
+ const accessControlColumn: string = accessControlColumnName;
464
503
  const labels: Array<AccessControlModel> =
465
504
  ((item as any)[
466
505
  accessControlColumn
@@ -516,8 +555,7 @@ const ModelForm: <TBaseModel extends BaseModel>(
516
555
  const options: Array<CategoryCheckboxOption> = [];
517
556
 
518
557
  for (const item of listResult.data) {
519
- const accessControlColumn: string =
520
- tempModel.getAccessControlColumn()!;
558
+ const accessControlColumn: string = accessControlColumnName;
521
559
  const labels: Array<AccessControlModel> =
522
560
  ((item as any)[
523
561
  accessControlColumn
@@ -554,9 +592,8 @@ const ModelForm: <TBaseModel extends BaseModel>(
554
592
  options: options,
555
593
  },
556
594
  accessControlColumnTitle:
557
- tempModel.getTableColumnMetadata(
558
- tempModel.getAccessControlColumn()!,
559
- ).title || "",
595
+ tempModel.getTableColumnMetadata(accessControlColumnName)
596
+ .title || "",
560
597
  };
561
598
  }
562
599
  } else {