searchResult.tsx 3.7 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140
  1. import {Fragment} from 'react';
  2. // eslint-disable-next-line no-restricted-imports
  3. import {withRouter, WithRouterProps} from 'react-router';
  4. import styled from '@emotion/styled';
  5. import DocIntegrationAvatar from 'sentry/components/avatar/docIntegrationAvatar';
  6. import SentryAppAvatar from 'sentry/components/avatar/sentryAppAvatar';
  7. import IdBadge from 'sentry/components/idBadge';
  8. import {IconInput, IconLink, IconSettings} from 'sentry/icons';
  9. import PluginIcon from 'sentry/plugins/components/pluginIcon';
  10. import space from 'sentry/styles/space';
  11. import highlightFuseMatches from 'sentry/utils/highlightFuseMatches';
  12. import {Result} from './sources/types';
  13. type Props = WithRouterProps<{orgId: string}> & {
  14. highlighted: boolean;
  15. item: Result['item'];
  16. matches: Result['matches'];
  17. };
  18. function renderResultType({resultType, model}: Result['item']) {
  19. switch (resultType) {
  20. case 'settings':
  21. return <IconSettings />;
  22. case 'field':
  23. return <IconInput />;
  24. case 'route':
  25. return <IconLink />;
  26. case 'integration':
  27. return <StyledPluginIcon pluginId={model.slug} />;
  28. case 'sentryApp':
  29. return <SentryAppAvatar sentryApp={model} />;
  30. case 'docIntegration':
  31. return <DocIntegrationAvatar docIntegration={model} />;
  32. default:
  33. return null;
  34. }
  35. }
  36. function SearchResult({item, matches, params, highlighted}: Props) {
  37. const {sourceType, model, extra} = item;
  38. function renderContent() {
  39. let {title, description} = item;
  40. if (matches) {
  41. // TODO(ts) Type this better.
  42. const HighlightedMarker = (p: any) => (
  43. <HighlightMarker data-test-id="highlight" highlighted={highlighted} {...p} />
  44. );
  45. const matchedTitle = matches && matches.find(({key}) => key === 'title');
  46. const matchedDescription =
  47. matches && matches.find(({key}) => key === 'description');
  48. title = matchedTitle
  49. ? highlightFuseMatches(matchedTitle, HighlightedMarker)
  50. : title;
  51. description = matchedDescription
  52. ? highlightFuseMatches(matchedDescription, HighlightedMarker)
  53. : description;
  54. }
  55. if (['organization', 'member', 'project', 'team'].includes(sourceType)) {
  56. const DescriptionNode = (
  57. <BadgeDetail highlighted={highlighted}>{description}</BadgeDetail>
  58. );
  59. const badgeProps = {
  60. displayName: title,
  61. displayEmail: DescriptionNode,
  62. description: DescriptionNode,
  63. useLink: false,
  64. orgId: params.orgId,
  65. avatarSize: 32,
  66. [sourceType]: model,
  67. };
  68. return <IdBadge {...badgeProps} />;
  69. }
  70. return (
  71. <Fragment>
  72. <div>{title}</div>
  73. {description && <SearchDetail>{description}</SearchDetail>}
  74. {extra && <ExtraDetail>{extra}</ExtraDetail>}
  75. </Fragment>
  76. );
  77. }
  78. return (
  79. <Wrapper>
  80. <Content>{renderContent()}</Content>
  81. <div>{renderResultType(item)}</div>
  82. </Wrapper>
  83. );
  84. }
  85. export default withRouter(SearchResult);
  86. const SearchDetail = styled('div')`
  87. font-size: 0.8em;
  88. line-height: 1.3;
  89. margin-top: 4px;
  90. opacity: 0.8;
  91. `;
  92. const ExtraDetail = styled('div')`
  93. font-size: ${p => p.theme.fontSizeSmall};
  94. color: ${p => p.theme.gray300};
  95. margin-top: ${space(0.5)};
  96. `;
  97. const BadgeDetail = styled('div')<{highlighted: boolean}>`
  98. line-height: 1.3;
  99. color: ${p => (p.highlighted ? p.theme.purple300 : null)};
  100. `;
  101. const Wrapper = styled('div')`
  102. display: flex;
  103. justify-content: space-between;
  104. align-items: center;
  105. `;
  106. const Content = styled('div')`
  107. display: flex;
  108. flex-direction: column;
  109. `;
  110. const StyledPluginIcon = styled(PluginIcon)`
  111. flex-shrink: 0;
  112. `;
  113. const HighlightMarker = styled('mark')`
  114. padding: 0;
  115. background: transparent;
  116. font-weight: bold;
  117. color: ${p => p.theme.active};
  118. `;