react-select#ValueType TypeScript Examples

The following examples show how to use react-select#ValueType. You can vote up the ones you like or vote down the ones you don't like, and go to the original project or source file by following the links above each example. You may check out the related API usage on the sidebar.
Example #1
Source File: utils.ts    From utopia with MIT License 7 votes vote down vote up
/**
 * A type guard for React Select's onChange values, which can either be a value
 * or an array of values.
 */
export function isOptionsType<T extends OptionTypeBase>(
  value: ValueType<T>,
): value is OptionsType<T> {
  return Array.isArray(value)
}
Example #2
Source File: className-subsection.tsx    From utopia with MIT License 6 votes vote down vote up
function valueTypeAsArray<T>(valueType: ValueType<T>): ReadonlyArray<T> {
  if (valueType == null) {
    return []
  } else if (isOptionsType(valueType)) {
    return valueType
  } else {
    return [valueType]
  }
}
Example #3
Source File: utils.ts    From utopia with MIT License 6 votes vote down vote up
/**
 * A type guard for React Select's onChange values, which can either be a value
 * or an array of values.
 */
export function isOptionType<T extends OptionTypeBase>(value: ValueType<T>): value is T {
  return value != null && !Array.isArray(value)
}
Example #4
Source File: popup-list.tsx    From utopia with MIT License 6 votes vote down vote up
getValueOfValueType = (value: ValueType<SelectOption>): SelectOption['value'] => {
  if (Array.isArray(value)) {
    if (value.length > 0) {
      return value[0].value
    } else {
      return undefined
    }
  } else {
    return (value as unknown as SelectOption).value
  }
}
Example #5
Source File: popup-list.tsx    From utopia with MIT License 6 votes vote down vote up
getIndexOfValue = (
  value: ValueType<SelectOption>,
  options: OptionsType<SelectOption>,
): number => {
  const firstValue = getValueOfValueType(value)

  let allOptions: SelectOption[] = []
  options.forEach((option) => {
    allOptions.push(option)
    if (option.options != null) {
      allOptions.push(...option.options)
    }
  })

  const index = allOptions.findIndex((option) => option.value === firstValue)
  return Math.max(0, index)
}
Example #6
Source File: GuideSelect.tsx    From fhir-validator-app with Apache License 2.0 6 votes vote down vote up
export function GuideSelect({ igs }: GuideSelectProps): ReactElement {
  const [formState, dispatch] = useContext(FormContext);
  const value = formState.implementationGuide;
  const empty: (SelectOption | null | undefined)[] = [];
  const handleChange = (value: ValueType<SelectOption>): void =>
    dispatch({ name: 'implementationGuide', value: empty.concat(value)[0] });
  const options = igs?.map((ig) => new SelectOption(ig, ig));

  return (
    <div>
      <label htmlFor="implementation-guide">
        Pick an Implementation Guide to validate against:
      </label>
      <Select
        isClearable
        isLoading={!options}
        options={options}
        name="implementation-guide"
        id="implementation-guide"
        value={value}
        onChange={handleChange}
      />
    </div>
  );
}
Example #7
Source File: ProfileSelect.tsx    From fhir-validator-app with Apache License 2.0 6 votes vote down vote up
export function ProfileSelect({ options }: ProfileSelectProps): ReactElement {
  const [formState, dispatch] = useContext(FormContext);
  const ig = formState.implementationGuide?.value;
  const value = formState.profileSelect;
  const handleChange = (value: ValueType<SelectOption>): void =>
    dispatch({ name: 'profileSelect', value });

  return (
    <div>
      <label htmlFor="profile-select">Select a profile:</label>
      <Select
        isClearable
        isDisabled={!ig}
        isLoading={!!ig && !options}
        options={options}
        name="profile-select"
        id="profile-select"
        value={value}
        onChange={handleChange}
      />
    </div>
  );
}
Example #8
Source File: SelectWidget.tsx    From ke with MIT License 5 votes vote down vote up
BaseSelectWidget = forwardRef<HTMLSelectElement, BaseSelectWidgetProps>(
  (props: BaseSelectWidgetProps, ref): JSX.Element => {
    const {
      name,
      helpText,
      description,
      displayValue,
      containerStore,
      mainDetailObject,
      data,
      style,
      setInitialValue,
      handleChange,
      required,
      isDisabled = false,
      isClearable = false,
      placeholder = 'Выберите значение',
      containerProps,
      labelContainerProps,
      widgetStyles: extenrnalStyles,
    } = props

    const context = containerStore.getState()

    const [value, label] = getSelectContent(name, mainDetailObject, displayValue, context)
    const isRequired = getAccessorWithDefault(required, mainDetailObject, context, false)

    const [resultOptions, setResultOptions] = useState<SelectObject[]>([])
    setInitialValue({ [name]: value })

    const widgetStyles = {
      menuPortal: (base: object) => ({ ...base, zIndex: 9999 }),
      ...extenrnalStyles,
    }

    useEffect(() => {
      const responseOptions = getAccessor(data, mainDetailObject, context)
      return applyCallback(responseOptions, setResultOptions)
    }, [data, mainDetailObject, context])

    const formatOption = (option: { value: any; label?: string; text?: string }): { value: any; label: string } => ({
      value: option.value,
      label: option?.label || option?.text || '',
    })

    const options = React.useMemo(() => resultOptions.map((option) => formatOption(option)), [resultOptions])

    const { getDataTestId } = useCreateTestId()

    return (
      <WidgetWrapper
        name={name}
        style={style}
        helpText={helpText}
        description={description}
        required={isRequired}
        containerProps={containerProps}
        labelContainerProps={labelContainerProps}
        {...getDataTestId(props)}
      >
        <Select
          inputRef={ref}
          options={options}
          defaultValue={value ? { value, label } : undefined}
          onChange={(changeValue: ValueType<object | object[], boolean>) => handleChange(changeValue)}
          styles={modifyStyles(widgetStyles)}
          isDisabled={isDisabled}
          components={components}
          isClearable={isClearable}
          placeholder={placeholder}
          name={name}
        />
      </WidgetWrapper>
    )
  }
)
Example #9
Source File: MultiSelectWidget.tsx    From ke with MIT License 4 votes vote down vote up
MultiSelectWidget = (props: MultiSelectWidgetProps): JSX.Element => {
  const {
    name,
    mainDetailObject,
    optionLabel,
    optionValue,
    style,
    helpText,
    provider,
    setInitialValue,
    submitChange,
    containerStore,
    cacheTime,
    targetPayload,
    optionLabelMenu,
    optionLabelValue,
    staleTime,
    componentsClasses,
  } = props

  const context = containerStore.getState()
  const { targetUrl, content, dataResourceUrl, isRequired, widgetDescription } = useWidgetInitialization({
    ...props,
    context,
  })
  const effectiveCacheTime = getAccessor(cacheTime, mainDetailObject, context)

  const [value, setValue] = React.useState<object[] | null>(content as object[] | null)
  setInitialValue(value ? getPayload(value, name, targetPayload) : null)

  const handleChange = (changeValue: ValueType<MultiSelectValue, true>): void => {
    setValue(changeValue as MultiSelectValue[])

    const widgetPayload = getPayload(changeValue, name, targetPayload)

    pushAnalytics({
      eventName: EventNameEnum.FOREIGN_KEY_SELECT_OPTION_CHANGE,
      widgetType: WidgetTypeEnum.INPUT,
      value: widgetPayload,
      objectForAnalytics: mainDetailObject,
      ...props,
    })

    submitChange({ url: targetUrl, payload: widgetPayload })
  }

  React.useMemo(() => {
    setValue(content as object[] | null)
  }, [content])

  const { getDataTestId } = useCreateTestId()

  return (
    <WidgetWrapper
      name={name}
      style={style}
      helpText={helpText}
      description={widgetDescription}
      required={isRequired}
      {...getDataTestId(props)}
    >
      <AsyncSelectWidget
        provider={provider}
        cacheTime={effectiveCacheTime}
        dataResourceUrl={dataResourceUrl}
        handleChange={handleChange}
        closeMenuOnSelect={false}
        value={value}
        isMulti
        getOptionLabel={(val: object | null) => optionLabel(val, mainDetailObject)}
        getOptionValue={optionValue}
        getOptionLabelMenu={
          optionLabelMenu ? (val: object | null) => optionLabelMenu(val, mainDetailObject) : undefined
        }
        getOptionLabelValue={
          optionLabelValue ? (val: object | null) => optionLabelValue(val, mainDetailObject) : undefined
        }
        staleTime={staleTime}
        componentsClasses={componentsClasses}
      />
    </WidgetWrapper>
  )
}
Example #10
Source File: AsyncSelectWidget.tsx    From ke with MIT License 4 votes vote down vote up
AsyncSelectWidget = ({
  dataResourceUrl,
  handleChange,
  value,
  getOptionLabel,
  getOptionValue,
  styles,
  isClearable = false,
  isMulti = false,
  defaultOptions = false,
  searchParamName = 'search',
  placeholder = 'Введите значение',
  getOptionLabelMenu,
  getOptionLabelValue,
  additionalValues = [],
  isDisabled = false,
  menuPlacement,
  className,
  staleTime,
  componentsClasses,
  name,
}: AsyncSelectWidgetProps): JSX.Element => {
  const debounceValue = 500

  const widgetStyles = {
    ...{
      menuPortal: (base: object) => ({ ...base, zIndex: 9999 }),
    },
    ...(styles !== undefined ? styles : {}),
  }

  const additionalValuesFromAccessor = getAccessor(additionalValues)

  const formatOptionLabel = (
    option: object | object[] | null,
    { context }: { context: 'menu' | 'value' }
  ): string | null => {
    if (!option) {
      return option
    }
    if (context === 'menu') {
      return getOptionLabelMenu ? getOptionLabelMenu(option) : getOptionLabel(option)
    }
    return getOptionLabelValue ? getOptionLabelValue(option) : getOptionLabel(option)
  }

  const { resourceKey, params } = useMemo(() => {
    const url = new URL(dataResourceUrl)
    return {
      resourceKey: url.origin.concat(url.pathname),
      params: Object.fromEntries(url.searchParams.entries()),
    }
  }, [dataResourceUrl])

  const cacheUniqs = useMemo(() => [dataResourceUrl], [dataResourceUrl])

  return (
    <StatefullAsyncSelect
      resource={{
        key: resourceKey,
        requestConfig: {
          params,
        },
        staleTime: getAccessor(staleTime),
      }}
      value={value}
      onChange={(changeValue: ValueType<object | object[], boolean>) => handleChange(changeValue)}
      defaultOptions={defaultOptions || additionalValuesFromAccessor}
      isClearable={isClearable}
      isMulti={isMulti as false | undefined}
      menuPortalTarget={document.body}
      styles={modifyStyles(widgetStyles)}
      formatOptionLabel={formatOptionLabel}
      getOptionValue={(option: object | object[] | null) => (option ? getOptionValue(option) : option)}
      placeholder={placeholder}
      isDisabled={isDisabled}
      menuPlacement={menuPlacement}
      className={className}
      components={components}
      debounceTimeout={debounceValue}
      searchParamName={searchParamName}
      cacheUniqs={cacheUniqs}
      componentsClasses={componentsClasses}
      name={name}
    />
  )
}
Example #11
Source File: preview-devices.tsx    From utopia with MIT License 4 votes vote down vote up
PreviewReactSelectDeviceSelector: React.FunctionComponent<
  React.PropsWithChildren<PreviewReactSelectDeviceSelectorProps>
> = ({ value, onChange, caratOffset }) => {
  const PreviewReactSelectSingleValue = (singleValueProps: any) => {
    return components.SingleValue == null ? null : (
      <components.SingleValue {...singleValueProps}>
        <span
          style={{
            whiteSpace: 'nowrap',
            maxWidth: '10em',
            textOverflow: 'ellipsis',
            overflow: 'hidden',
            display: 'inline-block',
            paddingRight: '.3em',
          }}
        >
          {singleValueProps.children}
        </span>
        <svg
          width='9'
          height='5'
          viewBox='0 0 9 5'
          xmlns='http://www.w3.org/2000/svg'
          style={{
            position: 'relative',
            bottom: caratOffset,
          }}
        >
          <path
            id='dropdown_control'
            d='M1,1 C5,4.66 3,4.66 7,1'
            strokeWidth='1'
            fill='none'
            stroke='rgb(155, 155, 155)'
          />
        </svg>
      </components.SingleValue>
    )
  }

  const selectOnChange = React.useCallback(
    (newValue: ValueType<DeviceReactSelectOption>) => {
      if (newValue != null && !Array.isArray(newValue)) {
        onChange(newValue as DeviceReactSelectOption)
      }
    },
    [onChange],
  )

  return (
    <Select
      className={``}
      classNamePrefix='preview-lightselect'
      menuShouldScrollIntoView={true}
      menuPlacement='auto'
      value={value}
      isClearable={false}
      isSearchable={false}
      isDisabled={false}
      components={{ SingleValue: PreviewReactSelectSingleValue }}
      onChange={selectOnChange}
      options={deviceReactSelectOptionList}
      styles={{
        container: (base: any, state: any) => {
          return {
            ...base,
            backgroundColor: 'transparent',
            boxShadow: 'none',
            textAlign: 'left',
            cursor: 'default',
          }
        },
        menu: (base: any, state: any) => {
          return {
            ...base,
            width: '200px !important',
            left: -12,
          }
        },
        control: (base: any, state: any) => {
          return {
            display: 'inline-block',
          }
        },
        valueContainer: (base: any, state: any) => {
          return {
            display: 'inline-block',
          }
        },
        singleValue: (base: any, state: any) => {
          return {
            display: 'inline-block',
            height: 18,
            position: 'relative',
          }
        },
        indicatorsContainer: () => {
          return {
            display: 'none',
          }
        },
      }}
    />
  )
}
Example #12
Source File: className-subsection.tsx    From utopia with MIT License 4 votes vote down vote up
ClassNameControl = React.memo(() => {
  const editorStoreRef = useRefEditorState((store) => store)
  const theme = useColorTheme()
  const targets = useEditorState(
    (store) => store.editor.selectedViews,
    'ClassNameSubsection targets',
  )
  const dispatch = useEditorState((store) => store.dispatch, 'ClassNameSubsection dispatch')

  const [filter, setFilter] = React.useState('')
  const isFocusedRef = React.useRef(false)
  const shouldPreviewOnFocusRef = React.useRef(false)
  const updateFocusedOption = usePubSubAtomWriteOnly(focusedOptionAtom)
  const focusedValueRef = React.useRef<string | null>(null)

  const focusTriggerCount = useEditorState(
    (store) => store.editor.inspector.classnameFocusCounter,
    'ClassNameSubsection classnameFocusCounter',
  )
  const inputRef = useInputFocusOnCountIncrease<CreatableSelect<TailWindOption>>(focusTriggerCount)

  const clearFocusedOption = React.useCallback(() => {
    shouldPreviewOnFocusRef.current = false
    updateFocusedOption(null)
    dispatch([EditorActions.clearTransientProps()], 'canvas')
  }, [updateFocusedOption, dispatch])

  const onBlur = React.useCallback(() => {
    isFocusedRef.current = false
    shouldPreviewOnFocusRef.current = false
    clearFocusedOption()
  }, [clearFocusedOption])

  const onFocus = React.useCallback(() => {
    isFocusedRef.current = true
  }, [])

  const options = useFilteredOptions(filter, 100)

  React.useEffect(() => {
    return function cleanup() {
      dispatch([EditorActions.clearTransientProps()], 'canvas')
    }
    /** deps is explicitly empty */
    // eslint-disable-next-line react-hooks/exhaustive-deps
  }, [])

  const { selectedClasses, elementPaths, isSettable } = useGetSelectedClasses()
  const selectedOptions = selectedClasses.map(getTailwindOptionForClassName)
  const elementPath = elementPaths[0]
  const isMenuEnabled = isSettable && elementPaths.length === 1
  const selectedOptionsLength = selectedOptions?.length ?? 0
  const [isExpanded, setIsExpanded] = React.useState(selectedOptionsLength > 0)

  const expandSection = React.useCallback(() => {
    setIsExpanded(true)
    queuedFocusTimeout = window.setTimeout(() => inputRef.current?.focus(), 0)
  }, [inputRef])
  const contractSection = React.useCallback(() => {
    setIsExpanded(false)
    if (queuedFocusTimeout != null) {
      window.clearTimeout(queuedFocusTimeout)
    }
  }, [])

  const toggleIsExpanded = React.useCallback(() => {
    if (isExpanded) {
      contractSection()
    } else {
      expandSection()
    }
  }, [isExpanded, expandSection, contractSection])

  const triggerCountRef = React.useRef(focusTriggerCount)

  React.useEffect(() => {
    if (!isExpanded && focusTriggerCount > triggerCountRef.current) {
      triggerCountRef.current = focusTriggerCount
      expandSection()
    }
  }, [focusTriggerCount, isExpanded, expandSection])

  const onChange = React.useCallback(
    (newValueType: ValueType<TailWindOption>) => {
      // As the value of the dropdown is changing, hide the selection
      // controls so they can see the results of what they're doing.
      EditorActions.hideAndShowSelectionControls(dispatch)

      const newValue = valueTypeAsArray(newValueType)
      if (elementPath != null) {
        if (queuedDispatchTimeout != null) {
          window.clearTimeout(queuedDispatchTimeout)
          queuedDispatchTimeout = undefined
        }

        dispatch(
          [
            EditorActions.setProp_UNSAFE(
              elementPath,
              PP.create(['className']),
              jsxAttributeValue(newValue.map((value) => value.value).join(' '), emptyComments),
            ),
            EditorActions.clearTransientProps(),
          ],
          'everyone',
        )
      }
    },
    [dispatch, elementPath],
  )

  const onInputChange = React.useCallback(
    (newInput: string) => {
      if (newInput === '') {
        clearFocusedOption()
      }
      focusedValueRef.current = null
      setFilter(newInput)
    },
    [clearFocusedOption, setFilter],
  )

  const handleKeyDown = React.useCallback(
    (event: React.KeyboardEvent<HTMLElement>) => {
      // As someone is typing, hide the selection
      // controls so they can see the results of what they're doing.
      EditorActions.hideAndShowSelectionControls(dispatch)

      const shouldStopPreviewing =
        filter === '' && (event.key === 'ArrowLeft' || event.key === 'ArrowRight')

      if (shouldStopPreviewing) {
        clearFocusedOption()
      } else {
        shouldPreviewOnFocusRef.current = true
      }

      if (
        event.key === 'ArrowUp' ||
        event.key === 'ArrowDown' ||
        event.key === 'PageUp' ||
        event.key === 'PageDown' ||
        event.key === 'Home' ||
        event.key === 'End'
      ) {
        // Any of these keys will jump the focus to the menu
        focusedValueRef.current = null
      }

      if (
        filter === '' &&
        selectedOptions != null &&
        (event.key === 'Backspace' || event.key === 'Delete')
      ) {
        if (event.key === 'Delete' && focusedValueRef.current == null) {
          // prevent the default react-select behaviour here, as it will delete the last value
          // if nothing is focused, which feels wrong
          event.preventDefault()
        } else {
          const updatedFilterText = focusedValueRef.current ?? last(selectedOptions)?.label
          if (updatedFilterText != null) {
            setFilter(updatedFilterText)
          }
        }
      }

      if (event.key === 'Escape') {
        inputRef.current?.blur()
      }

      const namesByKey = applyShortcutConfigurationToDefaults(
        editorStoreRef.current.userState.shortcutConfig,
      )
      handleShortcuts(namesByKey, event.nativeEvent, null, {
        [UNDO_CHANGES_SHORTCUT]: () => {
          return dispatch([EditorActions.undo()])
        },
        [REDO_CHANGES_SHORTCUT]: () => {
          return dispatch([EditorActions.redo()])
        },
      })
    },
    [clearFocusedOption, dispatch, editorStoreRef, filter, inputRef, selectedOptions],
  )

  const multiValueLabel: styleFn = React.useCallback(
    (base, { isFocused }) => {
      const enabledColor = isFocused ? theme.inverted.textColor.value : theme.inverted.primary.value
      const color = isMenuEnabled ? enabledColor : theme.fg8.value
      const backgroundColor = isFocused ? theme.inverted.primary.value : theme.bg1.value
      return {
        ...base,
        label: 'multiValueLabel',
        display: 'flex',
        alignItems: 'center',
        paddingTop: 2,
        paddingBottom: 2,
        paddingLeft: 6,
        paddingRight: 0,
        fontSize: 9,
        borderRadius: 3,
        color: color,
        backgroundColor: backgroundColor,
      }
    },
    [isMenuEnabled, theme],
  )

  const multiValueRemove: styleFn = React.useCallback(
    (base, state) => ({
      label: 'multiValueRemove',
      width: isMenuEnabled ? 16 : 0,
      height: UtopiaTheme.layout.inputHeight.small,
      display: 'flex',
      alignItems: 'center',
      padding: 0,
      overflow: 'hidden',
      marginRight: 2,
      backgroundImage: `url(${
        (state.isFocused as boolean)
          ? UNSAFE_getIconURL('cross-in-translucent-circle', 'blue')
          : UNSAFE_getIconURL('cross-small')
      })`,
      backgroundSize: 16,
      backgroundPosition: 'center center',
      ':hover': {
        backgroundImage: `url(${UNSAFE_getIconURL('cross-in-translucent-circle', 'blue')})`,
      },
    }),
    [isMenuEnabled],
  )

  const multiValue: styleFn = React.useCallback(
    (base, { isFocused, data }) => {
      const backgroundColor = isFocused ? theme.inverted.primary.value : theme.bg1.value
      if (isFocused) {
        focusedValueRef.current = data.label
      }

      return {
        label: 'multiValue',
        fontWeight: 600,
        color: theme.emphasizedForeground.value,
        borderRadius: UtopiaTheme.inputBorderRadius,
        display: 'flex',
        marginRight: 4,
        marginTop: 2,
        marginBottom: 2,
        minWidth: 0,
        height: UtopiaTheme.layout.inputHeight.small,
        boxShadow: `inset 0 0 0 1px ${
          isFocused ? theme.inspectorFocusedColor.value : 'transparent'
        }`,
        overflow: 'hidden',
        backgroundColor: backgroundColor,
      }
    },
    [theme],
  )

  const option: styleFn = React.useCallback(
    (base, { isFocused, isDisabled, value }) => {
      if (
        isFocusedRef.current &&
        shouldPreviewOnFocusRef.current &&
        isFocused &&
        targets.length === 1
      ) {
        const oldClassNameString =
          selectedOptions == null ? '' : selectedOptions.map((v) => v.value).join(' ') + ' '
        const newClassNameString = oldClassNameString + value
        if (queuedDispatchTimeout != null) {
          window.clearTimeout(queuedDispatchTimeout)
        }
        queuedDispatchTimeout = window.setTimeout(() => {
          dispatch(
            [
              EditorActions.setPropTransient(
                targets[0],
                PP.create(['className']),
                jsxAttributeValue(newClassNameString, emptyComments),
              ),
            ],
            'canvas',
          )
        }, 10)

        updateFocusedOption(value)
      }

      const color = isFocused ? theme.inverted.textColor.value : theme.textColor.value
      const backgroundColor = isFocused ? theme.inverted.primary.value : theme.bg1.value
      const borderRadius = isFocused ? 3 : 0

      return {
        minHeight: 27,
        display: 'flex',
        alignItems: 'center',
        paddingLeft: 8,
        paddingRight: 8,
        backgroundColor: backgroundColor,
        color: color,
        cursor: isDisabled ? 'not-allowed' : 'default',
        borderRadius: borderRadius,
      }

      // return base
    },
    [dispatch, targets, selectedOptions, updateFocusedOption, theme],
  )

  return (
    <div
      style={{
        backgroundColor: theme.emphasizedBackground.value,
        boxShadow: `0px 0px 1px 0px ${theme.neutralInvertedBackground.o(30).value}`,
        margin: 4,
      }}
    >
      <InspectorSubsectionHeader style={{ color: theme.primary.value }}>
        <span style={{ flexGrow: 1, cursor: 'pointer' }} onClick={toggleIsExpanded}>
          Class Names
        </span>
        <SquareButton highlight onClick={toggleIsExpanded}>
          <ExpandableIndicator visible collapsed={!isExpanded} selected={false} />
        </SquareButton>
      </InspectorSubsectionHeader>
      {when(
        isExpanded,
        <React.Fragment>
          <UIGridRow padded variant='<-------------1fr------------->'>
            <CreatableSelect
              ref={inputRef}
              autoFocus={false}
              placeholder={isMenuEnabled ? 'Add class…' : ''}
              isMulti
              value={selectedOptions}
              isDisabled={!isMenuEnabled}
              onChange={onChange}
              onInputChange={onInputChange}
              components={{
                IndicatorsContainer,
                Input,
                MultiValueRemove,
              }}
              className='className-inspector-control'
              styles={{
                container,
                control,
                valueContainer,
                multiValue,
                multiValueLabel,
                multiValueRemove,
                placeholder,
                menu,
                option,
              }}
              filterOption={AlwaysTrue}
              options={options}
              menuIsOpen={isMenuEnabled}
              onBlur={onBlur}
              onFocus={onFocus}
              escapeClearsValue={true}
              formatOptionLabel={formatOptionLabel}
              onKeyDown={handleKeyDown}
              maxMenuHeight={199}
              inputValue={filter}
            />
          </UIGridRow>
          {when(isMenuEnabled, <FooterSection options={options} filter={filter} />)}
        </React.Fragment>,
      )}
    </div>
  )
})
Example #13
Source File: popup-list.tsx    From utopia with MIT License 4 votes vote down vote up
getPortalPosition = (
  referenceTop: number,
  options: OptionsType<SelectOption>,
  value: ValueType<SelectOption>,
  scrollIndexOffset: number,
): {
  menuTop: number
  menuHeight: number
  scrollTop: number
  croppedTop: boolean
  croppedBottom: boolean
} => {
  const optionsLength = options.reduce((working, o) => {
    if (o.options == null) {
      return working + 1
    } else {
      return working + 1 + o.options.length
    }
  }, 0)
  const windowHeight = window.innerHeight
  const indexOfValue = getIndexOfValue(value, options)
  const centredIndex = indexOfValue + scrollIndexOffset

  const windowHeightAboveReference = referenceTop - WindowEdgePadding
  const windowHeightBelowReference =
    windowHeight - (windowHeightAboveReference + OptionHeight) - WindowEdgePadding

  const optionPaddingElements = 1
  const optionPaddingAboveSelected = OptionHeight * Math.min(optionPaddingElements, optionsLength)
  const optionPaddingBelowSelected =
    OptionHeight * Math.min(optionPaddingElements, optionsLength - centredIndex)

  if (
    windowHeightAboveReference > optionPaddingAboveSelected &&
    windowHeightBelowReference > optionPaddingBelowSelected
  ) {
    const numberCroppedTop = calculateOptionsToCutOff(
      optionsLength,
      windowHeightAboveReference,
      optionsLength - centredIndex - 1,
    )
    const howManyElementsToShowAboveSelected = centredIndex - numberCroppedTop
    const numberCroppedBottom = calculateOptionsToCutOff(
      optionsLength,
      windowHeightBelowReference,
      centredIndex,
    )
    const howManyElementsToShowBelowSelected =
      optionsLength - 1 - centredIndex - numberCroppedBottom
    const croppedMenuHeight =
      (howManyElementsToShowAboveSelected + howManyElementsToShowBelowSelected + 1) * OptionHeight
    return {
      menuTop:
        referenceTop - 2 * menuVerticalPadding - howManyElementsToShowAboveSelected * OptionHeight,
      menuHeight: croppedMenuHeight,
      scrollTop: numberCroppedTop * OptionHeight,
      croppedTop: numberCroppedTop > 0,
      croppedBottom: numberCroppedBottom > 0,
    }
  } else {
    if (windowHeightAboveReference > windowHeightBelowReference) {
      const numberCroppedTop = calculateOptionsToCutOff(optionsLength, windowHeightAboveReference)
      const numberCroppedBottom = calculateOptionsToCutOff(
        optionsLength,
        windowHeightBelowReference,
      )
      const menuHeight = Math.min(
        optionsLength * OptionHeight,
        windowHeightAboveReference - numberCroppedTop * OptionHeight,
      )
      return {
        menuTop: referenceTop - 2 * menuVerticalPadding - menuHeight,
        menuHeight: menuHeight,
        scrollTop: 0,
        croppedTop: numberCroppedTop > 0,
        croppedBottom: numberCroppedBottom > 0,
      }
    } else {
      const numberCroppedTop = calculateOptionsToCutOff(optionsLength, windowHeightAboveReference)
      const numberCroppedBottom = calculateOptionsToCutOff(
        optionsLength,
        windowHeightBelowReference,
      )
      const menuHeight = Math.min(
        optionsLength * OptionHeight,
        windowHeightBelowReference - numberCroppedBottom * OptionHeight,
      )
      return {
        menuTop: referenceTop - 2 * menuVerticalPadding + OptionHeight,
        menuHeight: menuHeight,
        scrollTop: 0,
        croppedTop: numberCroppedTop > 0,
        croppedBottom: numberCroppedBottom > 0,
      }
    }
  }
}
Example #14
Source File: popup-list.tsx    From utopia with MIT License 4 votes vote down vote up
PopupList = React.memo<PopupListProps>(
  React.forwardRef(
    (
      {
        id,
        options,
        value,
        onSubmitValue,
        style,
        containerMode = 'default',
        controlStyles = getControlStyles('simple'),
        disabled = !controlStyles.interactive,
      },
      ref,
    ) => {
      const selectOnSubmitValue = React.useCallback(
        (newValue: ValueType<SelectOption>) => {
          if (isOptionType(newValue)) {
            onSubmitValue(newValue)
          }
        },
        [onSubmitValue],
      )

      const container: styleFn = getContainer(containerMode, controlStyles, style)

      return (
        <Select
          id={id}
          components={{
            Option,
            MenuList,
            MenuPortal,
            DropdownIndicator,
            SingleValue,
          }}
          openMenuOnFocus={true}
          openMenuOnClick={true}
          value={value}
          onChange={selectOnSubmitValue}
          options={options}
          menuPortalTarget={document.getElementById(CommonUtils.PortalTargetID)}
          filterOption={createFilter({ ignoreAccents: true })}
          isDisabled={disabled}
          styles={{
            container,
            indicatorsContainer: (base) => ({
              ...base,
              width: 14,
              flexBasis: 14,
              background: 'transparent',
              // the control is on top of the input not inside it, so need to make space for input borders
              height: OptionHeight - 2,
              marginRight: 1,
              marginTop: 1,
              padding: 0,
              display: 'flex',
              alignItems: 'center',
              justifyContent: 'center',
              flexShrink: 0,
              '&:hover': {
                filter: 'brightness(.99)',
              },
              '&:active': {
                filter: 'brightness(.98)',
              },
            }),
            control: () => ({
              minHeight: OptionHeight,
              height: OptionHeight,
              backgroundColor: 'transparent',
              alignItems: 'center',
              cursor: 'default',
              display: 'flex',
              label: 'control',
              outline: '0 !important',
              position: 'relative',
              transition: 'all 100ms',
            }),
            singleValue: () => ({
              label: 'singleValue',
              color: controlStyles.mainColor,
              display: 'flex',
              alignItems: 'center',
              overflowX: 'scroll',
              whiteSpace: 'nowrap',
            }),
            menu: () => ({
              label: 'menu',
              boxSizing: 'border-box',
              height: '100%',
              width: '100%',
              padding: `${menuVerticalPadding}px 2px`,
            }),
            menuList: (_, menuListProps) => {
              return {
                padding: 0,
                boxSizing: 'border-box',
                label: 'menuList',
                height: menuListProps.children.length * OptionHeight,
              }
            },
            option: (_, optionProps) => ({
              paddingBottom: 0,
              paddingRight: '24px',
              paddingLeft: '4px',
              fontWeight: 400,
              userSelect: 'none',
              borderRadius: 2,
              fontSize: 11,
              backgroundColor:
                optionProps.isFocused === true
                  ? colorTheme.contextMenuHighlightBackground.value
                  : 'transparent',
              color:
                optionProps.isFocused === true
                  ? colorTheme.contextMenuHighlightForeground.value
                  : colorTheme.contextMenuForeground.value,
              height: OptionHeight,
              textTransform: 'capitalize',
            }),
            input: () => ({
              margin: 2,
              paddingBottom: 2,
              paddingTop: 2,
              visibility: 'visible',
              color: controlStyles.mainColor,
              boxSizing: 'border-box',
              position: 'absolute',
              top: 0,
              left: 0,
            }),
            valueContainer: () => ({
              label: 'valueContainer',
              height: OptionHeight,
              boxSizing: 'border-box',
              overflow: 'hidden',
              padding: `2px 2px 2px ${ValueContainerLeftPadding}px`,
              position: 'relative',
              borderRadius: UtopiaTheme.inputBorderRadius,
              display: 'flex',
              alignItems: 'center',
              flexGrow: containerMode === 'noBorder' ? 0 : 1,
            }),
            indicatorSeparator: displayNone,
            clearIndicator: displayNone,
            loadingIndicator: displayNone,
          }}
        />
      )
    },
  ),
)