styles.tsx 8.0 KB

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