searchResult.tsx 3.6 KB

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