styles.tsx 7.6 KB

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