breakdownBars.tsx 2.4 KB

1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889909192939495
  1. import {Fragment} from 'react';
  2. import styled from '@emotion/styled';
  3. import Tooltip from 'sentry/components/tooltip';
  4. import space from 'sentry/styles/space';
  5. import {formatPercentage} from 'sentry/utils/formatters';
  6. type Point = {
  7. label: string;
  8. value: number;
  9. active?: boolean;
  10. onClick?: () => void;
  11. tooltip?: string;
  12. };
  13. type Props = {
  14. /**
  15. * The data to display. The caller should order the points
  16. * in the order they want bars displayed.
  17. */
  18. data: Point[];
  19. maxItems?: number;
  20. };
  21. function BreakdownBars({data, maxItems}: Props) {
  22. const total = data.reduce((sum, point) => point.value + sum, 0);
  23. return (
  24. <BreakdownGrid>
  25. {(maxItems ? data.slice(0, maxItems) : data).map((point, i) => {
  26. const bar = (
  27. <Fragment>
  28. <Bar
  29. style={{width: `${((point.value / total) * 100).toFixed(2)}%`}}
  30. active={point.active}
  31. />
  32. <Label>{point.label}</Label>
  33. </Fragment>
  34. );
  35. return (
  36. <Fragment key={`${i}:${point.label}`}>
  37. <Percentage>{formatPercentage(point.value / total, 0)}</Percentage>
  38. <BarContainer
  39. data-test-id={`status-${point.label}`}
  40. cursor={point.onClick ? 'pointer' : 'default'}
  41. onClick={point.onClick}
  42. >
  43. {point.tooltip ? <Tooltip title={point.tooltip}>{bar}</Tooltip> : bar}
  44. </BarContainer>
  45. </Fragment>
  46. );
  47. })}
  48. </BreakdownGrid>
  49. );
  50. }
  51. export default BreakdownBars;
  52. const BreakdownGrid = styled('div')`
  53. display: grid;
  54. grid-template-columns: min-content auto;
  55. column-gap: ${space(1)};
  56. row-gap: ${space(1)};
  57. `;
  58. const Percentage = styled('div')`
  59. font-size: ${p => p.theme.fontSizeExtraLarge};
  60. text-align: right;
  61. `;
  62. const BarContainer = styled('div')<{cursor: 'pointer' | 'default'}>`
  63. padding-left: ${space(1)};
  64. padding-right: ${space(1)};
  65. position: relative;
  66. cursor: ${p => p.cursor};
  67. display: flex;
  68. align-items: center;
  69. `;
  70. const Label = styled('span')`
  71. position: relative;
  72. color: ${p => p.theme.textColor};
  73. z-index: 2;
  74. font-size: ${p => p.theme.fontSizeSmall};
  75. `;
  76. const Bar = styled('div')<{active?: boolean}>`
  77. border-radius: 2px;
  78. background-color: ${p => (p.active ? p.theme.purple200 : p.theme.border)};
  79. position: absolute;
  80. top: 0;
  81. left: 0;
  82. z-index: 1;
  83. height: 100%;
  84. width: 0%;
  85. `;