Browse Source

fix(compactSelect): Add support for sections in getLabel() (#33885)

* fix(compactSelect): Add support for sections in getLabel()

* ref(compactSelect): Small improvements to getSelectedOptions

Based on @JonasBa's suggestions in #33885.
Vu Luong 2 years ago
parent
commit
091bc1b05c
1 changed files with 33 additions and 5 deletions
  1. 33 5
      static/app/components/forms/compactSelect.tsx

+ 33 - 5
static/app/components/forms/compactSelect.tsx

@@ -16,7 +16,10 @@ import {useMenuTriggerState} from '@react-stately/menu';
 import Badge from 'sentry/components/badge';
 import Button from 'sentry/components/button';
 import DropdownButton, {DropdownButtonProps} from 'sentry/components/dropdownButtonV2';
-import SelectControl, {ControlProps} from 'sentry/components/forms/selectControl';
+import SelectControl, {
+  ControlProps,
+  GeneralSelectValue,
+} from 'sentry/components/forms/selectControl';
 import overflowEllipsis from 'sentry/styles/overflowEllipsis';
 import space from 'sentry/styles/space';
 
@@ -25,7 +28,11 @@ interface TriggerRenderingProps {
   ref: React.RefObject<HTMLButtonElement>;
 }
 
-interface Props extends ControlProps, Partial<OverlayProps>, Partial<AriaPositionProps> {
+interface Props<OptionType>
+  extends Omit<ControlProps<OptionType>, 'choices'>,
+    Partial<OverlayProps>,
+    Partial<AriaPositionProps> {
+  options: Array<OptionType & {options?: OptionType[]}>;
   /**
    * Pass class name to the outer wrap
    */
@@ -54,6 +61,25 @@ interface Props extends ControlProps, Partial<OverlayProps>, Partial<AriaPositio
   triggerProps?: DropdownButtonProps;
 }
 
+/**
+ * Recursively finds the selected option(s) from an options array. Useful for
+ * non-flat arrays that contain sections (groups of options).
+ */
+function getSelectedOptions<OptionType extends GeneralSelectValue = GeneralSelectValue>(
+  opts: Props<OptionType>['options'],
+  value: Props<OptionType>['value']
+): Props<OptionType>['options'] {
+  return opts.reduce((acc: Props<OptionType>['options'], cur) => {
+    if (cur.options) {
+      return acc.concat(getSelectedOptions(cur.options, value));
+    }
+    if (cur.value === value) {
+      return acc.concat(cur);
+    }
+    return acc;
+  }, []);
+}
+
 // Exported so we can further customize this component with react-select's
 // components prop elsewhere
 export const CompactSelectControl = ({
@@ -87,7 +113,7 @@ export const CompactSelectControl = ({
  * A select component with a more compact trigger button. Accepts the same
  * props as SelectControl, plus some more for the trigger button & overlay.
  */
-function CompactSelect({
+function CompactSelect<OptionType extends GeneralSelectValue = GeneralSelectValue>({
   // Select props
   options,
   onChange,
@@ -113,7 +139,7 @@ function CompactSelect({
   isDismissable = true,
   menuTitle,
   ...props
-}: Props) {
+}: Props<OptionType>) {
   // Manage the dropdown menu's open state
   const isDisabled = disabledProp || options?.length === 0;
   const triggerRef = useRef<HTMLButtonElement>(null);
@@ -157,7 +183,9 @@ function CompactSelect({
   // Update the button label when the value changes
   function getLabel(newValue): React.ReactNode {
     const valueSet = Array.isArray(newValue) ? newValue : [newValue];
-    const optionSet = valueSet.map(val => options.find(opt => opt.value === val));
+    const optionSet = valueSet
+      .map(val => getSelectedOptions<OptionType>(options, val))
+      .flat();
     const firstOptionLabel = optionSet[0]?.label ?? '';
 
     return (