optionSelector.tsx 2.2 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081
  1. import {Fragment, useMemo} from 'react';
  2. import styled from '@emotion/styled';
  3. import CompactSelect from 'sentry/components/compactSelect';
  4. import FeatureBadge from 'sentry/components/featureBadge';
  5. import Truncate from 'sentry/components/truncate';
  6. import {SelectValue} from 'sentry/types';
  7. import {defined} from 'sentry/utils';
  8. type BaseProps = React.ComponentProps<typeof CompactSelect> & {
  9. options: SelectValue<string>[];
  10. title: string;
  11. featureType?: 'alpha' | 'beta' | 'new';
  12. };
  13. interface SingleProps extends Omit<BaseProps, 'onChange'> {
  14. onChange: (value: string) => void;
  15. selected: string;
  16. }
  17. interface MultipleProps extends Omit<BaseProps, 'onChange'> {
  18. onChange: (value: string[]) => void;
  19. selected: string[];
  20. }
  21. function OptionSelector<MultipleType extends boolean>({
  22. options,
  23. onChange,
  24. selected,
  25. title,
  26. featureType,
  27. multiple,
  28. ...rest
  29. }: MultipleType extends true ? MultipleProps : SingleProps) {
  30. const mappedOptions = useMemo(() => {
  31. return options.map(opt => ({
  32. ...opt,
  33. label: <Truncate value={String(opt.label)} maxLength={60} expandDirection="left" />,
  34. }));
  35. }, [options]);
  36. function isOptionDisabled(option) {
  37. return (
  38. // Option is explicitly marked as disabled
  39. option.disabled ||
  40. // The user has reached the maximum number of selections (3), and the option hasn't
  41. // yet been selected. These options should be disabled to visually indicate that the
  42. // user has reached the max.
  43. (multiple && selected.length === 3 && !selected.includes(option.value))
  44. );
  45. }
  46. return (
  47. <CompactSelect
  48. size="sm"
  49. options={mappedOptions}
  50. value={selected}
  51. onChange={option => {
  52. onChange(multiple ? option.map(o => o.value) : option.value);
  53. }}
  54. isOptionDisabled={isOptionDisabled}
  55. multiple={multiple}
  56. triggerProps={{
  57. borderless: true,
  58. prefix: (
  59. <Fragment>
  60. {title}
  61. {defined(featureType) ? <StyledFeatureBadge type={featureType} /> : null}
  62. </Fragment>
  63. ),
  64. }}
  65. position="bottom-end"
  66. {...rest}
  67. />
  68. );
  69. }
  70. const StyledFeatureBadge = styled(FeatureBadge)`
  71. margin-left: 0px;
  72. `;
  73. export default OptionSelector;