row.tsx 3.3 KB

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