styles.tsx 8.0 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317
  1. import styled from '@emotion/styled';
  2. import {Panel, PanelBody} from 'sentry/components/panels';
  3. import space from 'sentry/styles/space';
  4. export const GRID_HEAD_ROW_HEIGHT = 45;
  5. export const GRID_BODY_ROW_HEIGHT = 40;
  6. export const GRID_STATUS_MESSAGE_HEIGHT = GRID_BODY_ROW_HEIGHT * 4;
  7. /**
  8. * Local z-index stacking context
  9. * https://developer.mozilla.org/en-US/docs/Web/CSS/CSS_Positioning/Understanding_z_index/The_stacking_context
  10. */
  11. // Parent context is Panel
  12. const Z_INDEX_PANEL = 1;
  13. const Z_INDEX_GRID_STATUS = -1;
  14. const Z_INDEX_GRID = 5;
  15. // Parent context is GridHeadCell
  16. const Z_INDEX_GRID_RESIZER = 1;
  17. export const Header = styled('div')`
  18. display: flex;
  19. justify-content: space-between;
  20. align-items: center;
  21. margin-bottom: ${space(1)};
  22. `;
  23. export const HeaderTitle = styled('h4')`
  24. margin: 0;
  25. font-size: ${p => p.theme.fontSizeMedium};
  26. color: ${p => p.theme.subText};
  27. `;
  28. export const HeaderButtonContainer = styled('div')`
  29. display: grid;
  30. gap: ${space(1)};
  31. grid-auto-flow: column;
  32. grid-auto-columns: auto;
  33. justify-items: end;
  34. /* Hovercard anchor element when features are disabled. */
  35. & > span {
  36. display: flex;
  37. flex-direction: row;
  38. }
  39. `;
  40. export const Body = styled(({children, ...props}) => (
  41. <Panel {...props}>
  42. <PanelBody>{children}</PanelBody>
  43. </Panel>
  44. ))`
  45. overflow: hidden;
  46. z-index: ${Z_INDEX_PANEL};
  47. `;
  48. /**
  49. * Grid is the parent element for the tableResizable component.
  50. *
  51. * On newer browsers, it will use CSS Grids to implement its layout.
  52. *
  53. * However, it is based on <table>, which has a distinction between header/body
  54. * HTML elements, which allows CSS selectors to its full potential. This has
  55. * the added advantage that older browsers will still have a chance of
  56. * displaying the data correctly (but this is untested).
  57. *
  58. * <thead>, <tbody>, <tr> are ignored by CSS Grid.
  59. * The entire layout is determined by the usage of <th> and <td>.
  60. */
  61. export const Grid = styled('table')<{height?: string | number; scrollable?: boolean}>`
  62. position: inherit;
  63. display: grid;
  64. /* Overwritten by GridEditable.setGridTemplateColumns */
  65. grid-template-columns: repeat(auto-fill, minmax(50px, auto));
  66. box-sizing: border-box;
  67. border-collapse: collapse;
  68. margin: 0;
  69. z-index: ${Z_INDEX_GRID};
  70. overflow-x: auto;
  71. overflow-y: ${p => (p.scrollable ? 'scroll' : 'hidden')};
  72. ${p =>
  73. p.height
  74. ? `
  75. height: 100%;
  76. max-height: ${typeof p.height === 'number' ? p.height + 'px' : p.height}
  77. `
  78. : ''}
  79. `;
  80. export const GridRow = styled('tr')`
  81. display: contents;
  82. &:last-child,
  83. &:last-child > td:first-child,
  84. &:last-child > td:last-child {
  85. border-bottom-left-radius: ${p => p.theme.borderRadius};
  86. border-bottom-right-radius: ${p => p.theme.borderRadius};
  87. }
  88. `;
  89. /**
  90. * GridHead is the collection of elements that builds the header section of the
  91. * Grid. As the entirety of the add/remove/resize actions are performed on the
  92. * header, most of the elements behave different for each stage.
  93. */
  94. export const GridHead = styled('thead')`
  95. display: contents;
  96. `;
  97. export const GridHeadCell = styled('th')<{isFirst: boolean; sticky?: boolean}>`
  98. /* By default, a grid item cannot be smaller than the size of its content.
  99. We override this by setting min-width to be 0. */
  100. position: relative; /* Used by GridResizer */
  101. height: ${GRID_HEAD_ROW_HEIGHT}px;
  102. display: flex;
  103. align-items: center;
  104. min-width: 24px;
  105. padding: 0 ${space(2)};
  106. border-right: 1px solid transparent;
  107. border-left: 1px solid transparent;
  108. background-color: ${p => p.theme.backgroundSecondary};
  109. color: ${p => p.theme.subText};
  110. font-size: ${p => p.theme.fontSizeSmall};
  111. font-weight: 600;
  112. text-transform: uppercase;
  113. user-select: none;
  114. ${p => (p.sticky ? `position: sticky; top: 0;` : '')}
  115. a,
  116. div,
  117. span {
  118. line-height: 1.1;
  119. color: inherit;
  120. white-space: nowrap;
  121. text-overflow: ellipsis;
  122. overflow: hidden;
  123. }
  124. &:first-child {
  125. border-top-left-radius: ${p => p.theme.borderRadius};
  126. }
  127. &:last-child {
  128. border-top-right-radius: ${p => p.theme.borderRadius};
  129. border-right: none;
  130. }
  131. &:hover {
  132. border-left-color: ${p => (p.isFirst ? 'transparent' : p.theme.border)};
  133. border-right-color: ${p => p.theme.border};
  134. }
  135. `;
  136. /**
  137. * Create spacing/padding similar to GridHeadCellWrapper but
  138. * without interactive aspects.
  139. */
  140. export const GridHeadCellStatic = styled('th')`
  141. height: ${GRID_HEAD_ROW_HEIGHT}px;
  142. display: flex;
  143. align-items: center;
  144. padding: 0 ${space(2)};
  145. background-color: ${p => p.theme.backgroundSecondary};
  146. font-size: ${p => p.theme.fontSizeSmall};
  147. font-weight: 600;
  148. line-height: 1;
  149. text-transform: uppercase;
  150. text-overflow: ellipsis;
  151. white-space: nowrap;
  152. overflow: hidden;
  153. &:first-child {
  154. border-top-left-radius: ${p => p.theme.borderRadius};
  155. padding: ${space(1)} 0 ${space(1)} ${space(3)};
  156. }
  157. `;
  158. /**
  159. * GridBody are the collection of elements that contains and display the data
  160. * of the Grid. They are rather simple.
  161. */
  162. export const GridBody = styled('tbody')`
  163. display: contents;
  164. > tr:first-child td {
  165. border-top: 1px solid ${p => p.theme.border};
  166. }
  167. `;
  168. export const GridBodyCell = styled('td')`
  169. /* By default, a grid item cannot be smaller than the size of its content.
  170. We override this by setting min-width to be 0. */
  171. min-width: 0;
  172. /* Locking in the height makes calculation for resizer to be easier.
  173. min-height is used to allow a cell to expand and this is used to display
  174. feedback during empty/error state */
  175. min-height: ${GRID_BODY_ROW_HEIGHT}px;
  176. padding: ${space(1)} ${space(2)};
  177. background-color: ${p => p.theme.background};
  178. border-top: 1px solid ${p => p.theme.innerBorder};
  179. display: flex;
  180. flex-direction: column;
  181. justify-content: center;
  182. font-size: ${p => p.theme.fontSizeMedium};
  183. &:first-child {
  184. padding: ${space(1)} 0 ${space(1)} ${space(3)};
  185. }
  186. &:last-child {
  187. border-right: none;
  188. padding: ${space(1)} ${space(2)};
  189. }
  190. `;
  191. const GridStatusWrapper = styled(GridBodyCell)`
  192. grid-column: 1 / -1;
  193. width: 100%;
  194. height: ${GRID_STATUS_MESSAGE_HEIGHT}px;
  195. background-color: transparent;
  196. `;
  197. const GridStatusFloat = styled('div')`
  198. position: absolute;
  199. top: 45px;
  200. left: 0;
  201. display: flex;
  202. justify-content: center;
  203. align-items: center;
  204. width: 100%;
  205. height: ${GRID_STATUS_MESSAGE_HEIGHT}px;
  206. z-index: ${Z_INDEX_GRID_STATUS};
  207. background: ${p => p.theme.background};
  208. `;
  209. export const GridBodyCellStatus = props => (
  210. <GridStatusWrapper>
  211. <GridStatusFloat>{props.children}</GridStatusFloat>
  212. </GridStatusWrapper>
  213. );
  214. /**
  215. * We have a fat GridResizer and we use the ::after pseudo-element to draw
  216. * a thin 1px border.
  217. *
  218. * The right most cell does not have a resizer as resizing from that side does strange things.
  219. */
  220. export const GridResizer = styled('div')<{dataRows: number}>`
  221. position: absolute;
  222. top: 0px;
  223. right: -6px;
  224. width: 11px;
  225. height: ${p => {
  226. const numOfRows = p.dataRows;
  227. let height = GRID_HEAD_ROW_HEIGHT + numOfRows * GRID_BODY_ROW_HEIGHT;
  228. if (numOfRows >= 1) {
  229. // account for border-bottom height
  230. height += numOfRows;
  231. }
  232. return height;
  233. }}px;
  234. padding-left: 5px;
  235. padding-right: 5px;
  236. cursor: col-resize;
  237. z-index: ${Z_INDEX_GRID_RESIZER};
  238. /**
  239. * This element allows us to have a fat GridResizer that is easy to hover and
  240. * drag, but still draws an appealing thin line for the border
  241. */
  242. &::after {
  243. content: ' ';
  244. display: block;
  245. width: 100%; /* Equivalent to 1px */
  246. height: 100%;
  247. }
  248. &:hover::after {
  249. background-color: ${p => p.theme.gray200};
  250. }
  251. /**
  252. * Ensure that this rule is after :hover, otherwise it will flicker when
  253. * the GridResizer is dragged
  254. */
  255. &:active::after,
  256. &:focus::after {
  257. background-color: ${p => p.theme.purple300};
  258. }
  259. /**
  260. * This element gives the resize handle a more visible knob to grab
  261. */
  262. &:hover::before {
  263. position: absolute;
  264. top: 0;
  265. left: 2px;
  266. content: ' ';
  267. display: block;
  268. width: 7px;
  269. height: ${GRID_HEAD_ROW_HEIGHT}px;
  270. background-color: ${p => p.theme.purple300};
  271. opacity: 0.4;
  272. }
  273. `;