truncate.tsx 3.5 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146
  1. import {Component} from 'react';
  2. import styled from '@emotion/styled';
  3. import space from 'sentry/styles/space';
  4. type DefaultProps = {
  5. className: string;
  6. expandDirection: 'left' | 'right';
  7. expandable: boolean;
  8. leftTrim: boolean;
  9. maxLength: number;
  10. minLength: number;
  11. };
  12. type Props = DefaultProps & {
  13. value: string;
  14. trimRegex?: RegExp;
  15. };
  16. type State = {
  17. isExpanded: boolean;
  18. };
  19. class Truncate extends Component<Props, State> {
  20. static defaultProps: DefaultProps = {
  21. className: '',
  22. minLength: 15,
  23. maxLength: 50,
  24. leftTrim: false,
  25. expandable: true,
  26. expandDirection: 'right',
  27. };
  28. state: State = {
  29. isExpanded: false,
  30. };
  31. onFocus = () => {
  32. const {value, maxLength} = this.props;
  33. if (value.length <= maxLength) {
  34. return;
  35. }
  36. this.setState({isExpanded: true});
  37. };
  38. onBlur = () => {
  39. if (this.state.isExpanded) {
  40. this.setState({isExpanded: false});
  41. }
  42. };
  43. render() {
  44. const {
  45. className,
  46. leftTrim,
  47. trimRegex,
  48. minLength,
  49. maxLength,
  50. value,
  51. expandable,
  52. expandDirection,
  53. } = this.props;
  54. const isTruncated = value.length > maxLength;
  55. let shortValue: React.ReactNode = '';
  56. if (isTruncated) {
  57. const slicedValue = leftTrim
  58. ? value.slice(value.length - (maxLength - 4), value.length)
  59. : value.slice(0, maxLength - 4);
  60. // Try to trim to values from the regex
  61. if (trimRegex && leftTrim) {
  62. const valueIndex = slicedValue.search(trimRegex);
  63. shortValue = (
  64. <span>
  65. …{' '}
  66. {valueIndex > 0 && valueIndex <= maxLength - minLength
  67. ? slicedValue.slice(slicedValue.search(trimRegex), slicedValue.length)
  68. : slicedValue}
  69. </span>
  70. );
  71. } else if (trimRegex && !leftTrim) {
  72. const matches = slicedValue.match(trimRegex);
  73. let lastIndex = matches
  74. ? slicedValue.lastIndexOf(matches[matches.length - 1]) + 1
  75. : slicedValue.length;
  76. if (lastIndex <= minLength) {
  77. lastIndex = slicedValue.length;
  78. }
  79. shortValue = <span>{slicedValue.slice(0, lastIndex)} …</span>;
  80. } else if (leftTrim) {
  81. shortValue = <span>… {slicedValue}</span>;
  82. } else {
  83. shortValue = <span>{slicedValue} …</span>;
  84. }
  85. } else {
  86. shortValue = value;
  87. }
  88. return (
  89. <Wrapper
  90. className={className}
  91. onMouseOver={expandable ? this.onFocus : undefined}
  92. onMouseOut={expandable ? this.onBlur : undefined}
  93. onFocus={expandable ? this.onFocus : undefined}
  94. onBlur={expandable ? this.onBlur : undefined}
  95. >
  96. <span>{shortValue}</span>
  97. {isTruncated && (
  98. <FullValue expanded={this.state.isExpanded} expandDirection={expandDirection}>
  99. {value}
  100. </FullValue>
  101. )}
  102. </Wrapper>
  103. );
  104. }
  105. }
  106. const Wrapper = styled('span')`
  107. position: relative;
  108. `;
  109. export const FullValue = styled('span')<{
  110. expandDirection: 'left' | 'right';
  111. expanded: boolean;
  112. }>`
  113. display: none;
  114. position: absolute;
  115. background: ${p => p.theme.background};
  116. padding: ${space(0.5)};
  117. border: 1px solid ${p => p.theme.innerBorder};
  118. white-space: nowrap;
  119. border-radius: ${space(0.5)};
  120. top: -5px;
  121. ${p => p.expandDirection === 'left' && 'right: -5px;'}
  122. ${p => p.expandDirection === 'right' && 'left: -5px;'}
  123. ${p =>
  124. p.expanded &&
  125. `
  126. z-index: ${p.theme.zIndex.truncationFullValue};
  127. display: block;
  128. `}
  129. `;
  130. export default Truncate;