row.tsx 3.3 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135
  1. import {memo, useEffect, useMemo} from 'react';
  2. import styled from '@emotion/styled';
  3. import AutoComplete from 'sentry/components/autoComplete';
  4. import InteractionStateLayer from 'sentry/components/interactionStateLayer';
  5. import {space} from 'sentry/styles/space';
  6. import {Item} from './types';
  7. type ItemSize = 'zero' | 'small';
  8. type AutoCompleteChildrenArgs<T extends Item> = Parameters<
  9. AutoComplete<T>['props']['children']
  10. >[0];
  11. type Props<T extends Item> = Pick<
  12. AutoCompleteChildrenArgs<T>,
  13. 'getItemProps' | 'registerVisibleItem' | 'inputValue'
  14. > &
  15. Omit<Parameters<AutoCompleteChildrenArgs<T>['getItemProps']>[0], 'index'> & {
  16. /**
  17. * Is the row 'active'
  18. */
  19. isHighlighted: boolean;
  20. /**
  21. * Size for dropdown items
  22. */
  23. itemSize?: ItemSize;
  24. /**
  25. * Style is used by react-virtualized for alignment
  26. */
  27. style?: React.CSSProperties;
  28. };
  29. function Row<T extends Item>({
  30. item,
  31. style,
  32. itemSize,
  33. isHighlighted,
  34. inputValue,
  35. getItemProps,
  36. registerVisibleItem,
  37. }: Props<T>) {
  38. const {index} = item;
  39. useEffect(() => registerVisibleItem(item.index, item), [registerVisibleItem, item]);
  40. const itemProps = useMemo(
  41. () => getItemProps({item, index}),
  42. [getItemProps, item, index]
  43. );
  44. if (item.groupLabel) {
  45. return (
  46. <LabelWithBorder style={style}>
  47. {item.label && <GroupLabel>{item.label}</GroupLabel>}
  48. </LabelWithBorder>
  49. );
  50. }
  51. return (
  52. <AutoCompleteItem
  53. itemSize={itemSize}
  54. disabled={item.disabled}
  55. isHighlighted={isHighlighted}
  56. style={style}
  57. {...itemProps}
  58. >
  59. <InteractionStateLayer isHovered={isHighlighted} />
  60. {typeof item.label === 'function' ? item.label({inputValue}) : item.label}
  61. </AutoCompleteItem>
  62. );
  63. }
  64. // XXX(epurkhiser): We memoize the row component since there will be many of
  65. // them, we do not want them re-rendering every time we change the
  66. // highlightedIndex in the parent List.
  67. export default memo(Row);
  68. const getItemPaddingForSize = (itemSize?: ItemSize) => {
  69. if (itemSize === 'small') {
  70. return `${space(0.5)} ${space(1)}`;
  71. }
  72. if (itemSize === 'zero') {
  73. return '0';
  74. }
  75. return space(1);
  76. };
  77. const LabelWithBorder = styled('div')`
  78. display: flex;
  79. align-items: center;
  80. background-color: ${p => p.theme.backgroundSecondary};
  81. border-bottom: 1px solid ${p => p.theme.innerBorder};
  82. border-width: 1px 0;
  83. color: ${p => p.theme.subText};
  84. font-size: ${p => p.theme.fontSizeMedium};
  85. :first-child {
  86. border-top: none;
  87. }
  88. :last-child {
  89. border-bottom: none;
  90. }
  91. `;
  92. const GroupLabel = styled('div')`
  93. padding: ${space(0.25)} ${space(1)};
  94. `;
  95. const AutoCompleteItem = styled('div')<{
  96. isHighlighted: boolean;
  97. disabled?: boolean;
  98. itemSize?: ItemSize;
  99. }>`
  100. position: relative;
  101. /* needed for virtualized lists that do not fill parent height */
  102. /* e.g. breadcrumbs (org height > project, but want same fixed height for both) */
  103. display: flex;
  104. flex-direction: column;
  105. justify-content: center;
  106. scroll-margin: 20px 0;
  107. font-size: ${p => p.theme.fontSizeMedium};
  108. color: ${p => (p.isHighlighted ? p.theme.textColor : 'inherit')};
  109. padding: ${p => getItemPaddingForSize(p.itemSize)};
  110. cursor: ${p => (p.disabled ? 'not-allowed' : 'pointer')};
  111. border-bottom: 1px solid ${p => p.theme.innerBorder};
  112. :last-child {
  113. border-bottom: none;
  114. }
  115. `;