scoreCard.tsx 2.8 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125
  1. import {Theme} from '@emotion/react';
  2. import styled from '@emotion/styled';
  3. import Panel from 'sentry/components/panels/panel';
  4. import QuestionTooltip from 'sentry/components/questionTooltip';
  5. import TextOverflow from 'sentry/components/textOverflow';
  6. import {space} from 'sentry/styles/space';
  7. import {defined} from 'sentry/utils';
  8. export type ScoreCardProps = {
  9. title: React.ReactNode;
  10. className?: string;
  11. help?: React.ReactNode;
  12. isTooltipHoverable?: boolean;
  13. renderOpenButton?: () => React.ReactNode;
  14. score?: React.ReactNode;
  15. trend?: React.ReactNode;
  16. trendStatus?: 'good' | 'bad';
  17. };
  18. function ScoreCard({
  19. title,
  20. score,
  21. help,
  22. trend,
  23. trendStatus,
  24. className,
  25. renderOpenButton,
  26. isTooltipHoverable,
  27. }: ScoreCardProps) {
  28. return (
  29. <ScorePanel className={className}>
  30. <HeaderWrapper>
  31. <HeaderTitle>
  32. <Title>{title}</Title>
  33. {help && (
  34. <QuestionTooltip
  35. title={help}
  36. size="sm"
  37. position="top"
  38. isHoverable={isTooltipHoverable}
  39. />
  40. )}
  41. </HeaderTitle>
  42. {renderOpenButton?.()}
  43. </HeaderWrapper>
  44. <ScoreWrapper>
  45. <Score>{score ?? '\u2014'}</Score>
  46. {defined(trend) && (
  47. <Trend trendStatus={trendStatus}>
  48. <TextOverflow>{trend}</TextOverflow>
  49. </Trend>
  50. )}
  51. </ScoreWrapper>
  52. </ScorePanel>
  53. );
  54. }
  55. function getTrendColor(p: TrendProps & {theme: Theme}) {
  56. switch (p.trendStatus) {
  57. case 'good':
  58. return p.theme.successText;
  59. case 'bad':
  60. return p.theme.errorText;
  61. default:
  62. return p.theme.subText;
  63. }
  64. }
  65. export const ScorePanel = styled(Panel)`
  66. display: flex;
  67. flex-direction: column;
  68. justify-content: space-between;
  69. padding: ${space(2)} ${space(3)};
  70. min-height: 96px;
  71. `;
  72. export const HeaderTitle = styled('div')`
  73. display: inline-grid;
  74. grid-auto-flow: column;
  75. gap: ${space(1)};
  76. align-items: center;
  77. width: fit-content;
  78. `;
  79. export const Title = styled('div')`
  80. font-size: ${p => p.theme.fontSizeLarge};
  81. color: ${p => p.theme.headingColor};
  82. ${p => p.theme.overflowEllipsis};
  83. font-weight: 600;
  84. `;
  85. const HeaderWrapper = styled('div')`
  86. display: flex;
  87. flex-wrap: wrap;
  88. align-items: center;
  89. justify-content: space-between;
  90. `;
  91. export const ScoreWrapper = styled('div')`
  92. display: flex;
  93. flex-direction: row;
  94. align-items: baseline;
  95. max-width: 100%;
  96. `;
  97. export const Score = styled('span')`
  98. flex-shrink: 1;
  99. font-size: 32px;
  100. line-height: 1;
  101. color: ${p => p.theme.headingColor};
  102. white-space: nowrap;
  103. `;
  104. type TrendProps = {trendStatus: ScoreCardProps['trendStatus']};
  105. export const Trend = styled('div')<TrendProps>`
  106. color: ${getTrendColor};
  107. margin-left: ${space(1)};
  108. line-height: 1;
  109. overflow: hidden;
  110. `;
  111. export default ScoreCard;