theme.tsx 16 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723
  1. import '@emotion/react';
  2. import color from 'color';
  3. import CHART_PALETTE from 'app/constants/chartPalette';
  4. import {DataCategory} from 'app/types';
  5. const colors = {
  6. white: '#FFFFFF',
  7. black: '#1D1127',
  8. gray100: '#E7E1EC',
  9. gray200: '#C6BECF',
  10. gray300: '#9386A0',
  11. gray400: '#776589',
  12. gray500: '#2B1D38',
  13. yellow100: '#FDE8b4',
  14. yellow200: '#FFD577',
  15. yellow300: '#FFC227',
  16. purple100: '#D4D1EC',
  17. purple200: '#A396DA',
  18. purple300: '#6C5FC7',
  19. blue100: '#D2DFF7',
  20. blue200: '#6e9ef7',
  21. blue300: '#3D74DB',
  22. orange100: '#FFF1ED',
  23. orange200: '#F9C7B9',
  24. orange300: '#F69C7D',
  25. orange400: '#FF7738',
  26. orange500: '#BA4A23',
  27. red100: '#FCC6C8',
  28. red200: '#FD918F',
  29. red300: '#F55459',
  30. green100: '#B6ECDF',
  31. green200: '#7DD6BE',
  32. green300: '#33BF9E',
  33. pink100: '#FDC9D7',
  34. pink200: '#FA93AB',
  35. pink300: '#F05781',
  36. };
  37. /**
  38. * This is not in the gray palette because it should [generally] only be used for backgrounds
  39. */
  40. const backgroundSecondary = '#FAF9FB';
  41. const lightAliases = {
  42. /**
  43. * Primary text color
  44. */
  45. textColor: colors.gray500,
  46. /**
  47. * Text that should not have as much emphasis
  48. */
  49. subText: colors.gray400,
  50. /**
  51. * Background for the main content area of a page?
  52. */
  53. bodyBackground: backgroundSecondary,
  54. /**
  55. * Primary background color
  56. */
  57. background: colors.white,
  58. /**
  59. * Secondary background color used as a slight contrast against primary background
  60. */
  61. backgroundSecondary,
  62. /**
  63. * Background for the header of a page
  64. */
  65. headerBackground: colors.white,
  66. /**
  67. * Primary border color
  68. */
  69. border: colors.gray200,
  70. /**
  71. * Inner borders, e.g. borders inside of a grid
  72. */
  73. innerBorder: colors.gray100,
  74. /**
  75. * Border around modals
  76. */
  77. modalBorder: 'none',
  78. /**
  79. * Box shadow on the modal
  80. */
  81. modalBoxShadow: 'none',
  82. /**
  83. * A color that denotes a "success", or something good
  84. */
  85. success: colors.green300,
  86. /**
  87. * A color that denotes an error, or something that is wrong
  88. */
  89. error: colors.red300,
  90. /**
  91. * A color that indicates something is disabled where user can not interact or use
  92. * it in the usual manner (implies that there is an "enabled" state)
  93. */
  94. disabled: colors.gray200,
  95. /**
  96. * Indicates that something is "active" or "selected"
  97. */
  98. active: colors.purple300,
  99. /**
  100. * Indicates that something has "focus", which is different than "active" state as it is more temporal
  101. * and should be a bit subtler than active
  102. */
  103. focus: backgroundSecondary,
  104. /**
  105. * Inactive
  106. */
  107. inactive: colors.gray200,
  108. /**
  109. * Link color indicates that something is clickable
  110. */
  111. linkColor: colors.blue300,
  112. linkHoverColor: colors.blue300,
  113. /**
  114. * Secondary button colors
  115. */
  116. secondaryButtonBorder: colors.gray200,
  117. secondaryButtonText: colors.gray500,
  118. /**
  119. * Primary button colors
  120. */
  121. primaryButtonBorder: '#3d328e',
  122. primaryButtonBorderActive: '#352b7b',
  123. /**
  124. * Gradient for sidebar
  125. */
  126. sidebarGradient:
  127. 'linear-gradient(294.17deg,#2f1937 35.57%,#452650 92.42%,#452650 92.42%)',
  128. /**
  129. * Form placeholder text color
  130. */
  131. formPlaceholder: colors.gray200,
  132. /**
  133. * Default form text color
  134. */
  135. formText: colors.gray500,
  136. /**
  137. * Form input border
  138. */
  139. formInputBorder: colors.gray200,
  140. /**
  141. *
  142. */
  143. rowBackground: backgroundSecondary,
  144. /**
  145. * Color of lines that flow across the background of the chart to indicate axes levels
  146. * (This should only be used for yAxis)
  147. */
  148. chartLineColor: colors.gray100,
  149. /**
  150. * Color for chart label text
  151. */
  152. chartLabel: colors.gray200,
  153. /**
  154. * Default Progressbar color
  155. */
  156. progressBar: colors.purple300,
  157. /**
  158. * Default Progressbar color
  159. */
  160. progressBackground: colors.gray100,
  161. /**
  162. * Background of alerts
  163. */
  164. alertBackgroundAlpha: 0.3,
  165. /**
  166. * Background of default badge (mainly used in NavTabs)
  167. */
  168. badgeBackground: colors.gray200,
  169. /**
  170. * Overlay for partial opacity
  171. */
  172. overlayBackgroundAlpha: 'rgba(255, 255, 255, 0.7)',
  173. /**
  174. * Tag progress bars
  175. */
  176. tagBarHover: colors.purple200,
  177. tagBar: colors.gray200,
  178. /**
  179. * Color for badge text
  180. */
  181. badgeText: colors.white,
  182. /**
  183. * Search filter "token" background
  184. */
  185. searchTokenBackground: {
  186. valid: '#E8F3FE',
  187. validActive: color('#E8F3FE').darken(0.02).string(),
  188. invalid: colors.red100,
  189. invalidActive: color(colors.red100).darken(0.02).string(),
  190. },
  191. /**
  192. * Search filter "token" border
  193. */
  194. searchTokenBorder: {
  195. valid: '#B5DAFF',
  196. validActive: color('#B5DAFF').darken(0.15).string(),
  197. invalid: colors.red300,
  198. invalidActive: color(colors.red300).darken(0.15).string(),
  199. },
  200. /**
  201. * Count on button when active
  202. */
  203. buttonCountActive: colors.gray100,
  204. /**
  205. * Count on button
  206. */
  207. buttonCount: colors.gray400,
  208. /**
  209. * Background of alert banners at the top
  210. */
  211. bannerBackground: colors.black,
  212. };
  213. const dataCategory = {
  214. [DataCategory.ERRORS]: CHART_PALETTE[4][3],
  215. [DataCategory.TRANSACTIONS]: CHART_PALETTE[4][2],
  216. [DataCategory.ATTACHMENTS]: CHART_PALETTE[4][1],
  217. [DataCategory.DEFAULT]: CHART_PALETTE[4][0],
  218. };
  219. const generateAlertTheme = (alias: Aliases) => ({
  220. muted: {
  221. background: colors.gray200,
  222. backgroundLight: alias.backgroundSecondary,
  223. border: alias.border,
  224. iconColor: 'inherit',
  225. },
  226. info: {
  227. background: colors.blue300,
  228. backgroundLight: color(colors.blue100).alpha(alias.alertBackgroundAlpha).string(),
  229. border: colors.blue200,
  230. iconColor: colors.blue300,
  231. },
  232. warning: {
  233. background: colors.yellow300,
  234. backgroundLight: color(colors.yellow100).alpha(alias.alertBackgroundAlpha).string(),
  235. border: colors.yellow300,
  236. iconColor: colors.yellow300,
  237. },
  238. success: {
  239. background: colors.green300,
  240. backgroundLight: color(colors.green100).alpha(alias.alertBackgroundAlpha).string(),
  241. border: colors.green200,
  242. iconColor: colors.green300,
  243. },
  244. error: {
  245. background: colors.red300,
  246. backgroundLight: color(colors.red100).alpha(alias.alertBackgroundAlpha).string(),
  247. border: colors.red200,
  248. iconColor: colors.red300,
  249. textLight: colors.red200,
  250. },
  251. });
  252. const generateBadgeTheme = (alias: Aliases) => ({
  253. default: {
  254. background: alias.badgeBackground,
  255. indicatorColor: alias.badgeBackground,
  256. color: alias.badgeText,
  257. },
  258. alpha: {
  259. background: `linear-gradient(90deg, ${colors.pink300}, ${colors.yellow300})`,
  260. indicatorColor: colors.orange400,
  261. color: alias.badgeText,
  262. },
  263. beta: {
  264. background: `linear-gradient(90deg, ${colors.purple300}, ${colors.pink300})`,
  265. indicatorColor: colors.purple300,
  266. color: alias.badgeText,
  267. },
  268. new: {
  269. background: `linear-gradient(90deg, ${colors.blue300}, ${colors.green300})`,
  270. indicatorColor: colors.green300,
  271. color: alias.badgeText,
  272. },
  273. review: {
  274. background: colors.purple300,
  275. indicatorColor: colors.purple300,
  276. color: alias.badgeText,
  277. },
  278. warning: {
  279. background: colors.yellow300,
  280. indicatorColor: colors.yellow300,
  281. color: alias.badgeText,
  282. },
  283. });
  284. const tag = {
  285. default: {
  286. background: colors.gray100,
  287. iconColor: colors.purple300,
  288. },
  289. promotion: {
  290. background: colors.orange100,
  291. iconColor: colors.orange400,
  292. },
  293. highlight: {
  294. background: colors.purple100,
  295. iconColor: colors.purple300,
  296. },
  297. warning: {
  298. background: colors.yellow100,
  299. iconColor: colors.yellow300,
  300. },
  301. success: {
  302. background: colors.green100,
  303. iconColor: colors.green300,
  304. },
  305. error: {
  306. background: colors.red100,
  307. iconColor: colors.red300,
  308. },
  309. info: {
  310. background: colors.blue100,
  311. iconColor: colors.blue300,
  312. },
  313. white: {
  314. background: colors.white,
  315. iconColor: colors.gray500,
  316. },
  317. black: {
  318. background: colors.gray500,
  319. iconColor: colors.white,
  320. },
  321. };
  322. const level = {
  323. sample: colors.purple300,
  324. info: colors.blue300,
  325. warning: colors.yellow300,
  326. error: colors.orange400,
  327. fatal: colors.red300,
  328. default: colors.gray300,
  329. };
  330. const generateButtonTheme = (alias: Aliases) => ({
  331. borderRadius: '3px',
  332. default: {
  333. color: alias.secondaryButtonText,
  334. colorActive: alias.secondaryButtonText,
  335. background: alias.background,
  336. backgroundActive: alias.background,
  337. border: alias.secondaryButtonBorder,
  338. borderActive: alias.secondaryButtonBorder,
  339. focusShadow: color(colors.gray200).alpha(0.5).string(),
  340. },
  341. primary: {
  342. color: colors.white,
  343. colorActive: colors.white,
  344. background: colors.purple300,
  345. backgroundActive: '#4e3fb4',
  346. border: alias.primaryButtonBorder,
  347. borderActive: alias.primaryButtonBorderActive,
  348. focusShadow: color(colors.purple300).alpha(0.4).string(),
  349. },
  350. success: {
  351. color: colors.white,
  352. colorActive: colors.white,
  353. background: '#3fa372',
  354. backgroundActive: colors.green300,
  355. border: '#7ccca5',
  356. borderActive: '#7ccca5',
  357. focusShadow: color(colors.green300).alpha(0.5).string(),
  358. },
  359. danger: {
  360. color: colors.white,
  361. colorActive: colors.white,
  362. background: colors.red300,
  363. backgroundActive: '#bf2a1d',
  364. border: '#bf2a1d',
  365. borderActive: '#7d1c13',
  366. focusShadow: color(colors.red300).alpha(0.5).string(),
  367. },
  368. link: {
  369. color: colors.blue300,
  370. colorActive: colors.blue300,
  371. background: 'transparent',
  372. border: false,
  373. borderActive: false,
  374. backgroundActive: 'transparent',
  375. focusShadow: false,
  376. },
  377. disabled: {
  378. color: alias.disabled,
  379. colorActive: alias.disabled,
  380. border: alias.disabled,
  381. borderActive: alias.disabled,
  382. background: alias.background,
  383. backgroundActive: alias.background,
  384. focusShadow: false,
  385. },
  386. form: {
  387. color: alias.textColor,
  388. colorActive: alias.textColor,
  389. background: alias.background,
  390. backgroundActive: alias.background,
  391. border: alias.formInputBorder,
  392. borderActive: alias.formInputBorder,
  393. focusShadow: false,
  394. },
  395. });
  396. const iconSizes = {
  397. xs: '12px',
  398. sm: '16px',
  399. md: '20px',
  400. lg: '24px',
  401. xl: '32px',
  402. xxl: '72px',
  403. };
  404. const commonTheme = {
  405. breakpoints: ['800px', '992px', '1200px', '1440px', '2560px'],
  406. ...colors,
  407. iconSizes,
  408. iconDirections: {
  409. up: '0',
  410. right: '90',
  411. down: '180',
  412. left: '270',
  413. },
  414. // Try to keep these ordered plz
  415. zIndex: {
  416. // Generic z-index when you hope your component is isolated and
  417. // does not need to battle others for z-index priority
  418. initial: 1,
  419. truncationFullValue: 10,
  420. traceView: {
  421. spanTreeToggler: 900,
  422. dividerLine: 909,
  423. rowInfoMessage: 910,
  424. minimapContainer: 999,
  425. },
  426. header: 1000,
  427. errorMessage: 1000,
  428. dropdown: 1001,
  429. dropdownAutocomplete: {
  430. // needs to be below actor but above other page elements (e.g. pagination)
  431. // (e.g. Issue Details "seen" dots on chart is 2)
  432. // stream header is 1000
  433. menu: 1007,
  434. // needs to be above menu
  435. actor: 1008,
  436. },
  437. globalSelectionHeader: 1009,
  438. settingsSidebarNavMask: 1017,
  439. settingsSidebarNav: 1018,
  440. sidebarPanel: 1019,
  441. sidebar: 1020,
  442. orgAndUserMenu: 1030,
  443. // Sentry user feedback modal
  444. sentryErrorEmbed: 1090,
  445. // If you change modal also update shared-components.less
  446. // as the z-index for bootstrap modals lives there.
  447. modal: 10000,
  448. toast: 10001,
  449. // tooltips and hovercards can be inside modals sometimes.
  450. hovercard: 10002,
  451. tooltip: 10003,
  452. // On mobile views org stats dropdowns overlap
  453. orgStats: {
  454. dataCategory: 1,
  455. timeRange: 2,
  456. },
  457. // On mobile views issue list dropdowns overlap
  458. issuesList: {
  459. stickyHeader: 1,
  460. sortOptions: 2,
  461. displayOptions: 3,
  462. },
  463. },
  464. grid: 8,
  465. borderRadius: '4px',
  466. borderRadiusBottom: '0 0 4px 4px',
  467. borderRadiusTop: '4px 4px 0 0',
  468. headerSelectorRowHeight: 44,
  469. headerSelectorLabelHeight: 28,
  470. dropShadowLightest: '0 1px 2px rgba(0, 0, 0, 0.04)',
  471. dropShadowLight: '0 2px 0 rgba(37, 11, 54, 0.04)',
  472. dropShadowHeavy: '0 1px 4px 1px rgba(47,40,55,0.08), 0 4px 16px 0 rgba(47,40,55,0.12)',
  473. // Relative font sizes
  474. fontSizeRelativeSmall: '0.9em',
  475. fontSizeExtraSmall: '11px',
  476. fontSizeSmall: '12px',
  477. fontSizeMedium: '14px',
  478. fontSizeLarge: '16px',
  479. fontSizeExtraLarge: '18px',
  480. headerFontSize: '22px',
  481. settings: {
  482. // Max-width for settings breadcrumbs
  483. // i.e. organization, project, or team
  484. maxCrumbWidth: '240px',
  485. containerWidth: '1440px',
  486. headerHeight: '69px',
  487. sidebarWidth: '220px',
  488. },
  489. sidebar: {
  490. background: '#2f2936',
  491. color: '#9586a5',
  492. divider: '#493e54',
  493. badgeSize: '22px',
  494. smallBadgeSize: '11px',
  495. collapsedWidth: '70px',
  496. expandedWidth: '220px',
  497. mobileHeight: '54px',
  498. menuSpacing: '15px',
  499. },
  500. text: {
  501. family: '"Rubik", "Avenir Next", sans-serif',
  502. familyMono: '"IBM Plex", Monaco, Consolas, "Courier New", monospace',
  503. lineHeightHeading: '1.15',
  504. lineHeightBody: '1.4',
  505. },
  506. dataCategory,
  507. tag,
  508. level,
  509. charts: {
  510. colors: CHART_PALETTE[CHART_PALETTE.length - 1],
  511. // We have an array that maps `number + 1` --> list of `number` colors
  512. getColorPalette: (length: number) =>
  513. CHART_PALETTE[Math.min(CHART_PALETTE.length - 1, length + 1)] as string[],
  514. previousPeriod: colors.gray200,
  515. symbolSize: 6,
  516. },
  517. diff: {
  518. removedRow: 'hsl(358deg 89% 65% / 15%)',
  519. removed: 'hsl(358deg 89% 65% / 30%)',
  520. addedRow: 'hsl(100deg 100% 87% / 18%)',
  521. added: 'hsl(166deg 58% 47% / 32%)',
  522. },
  523. // Similarity spectrum used in "Similar Issues" in group details
  524. similarity: {
  525. empty: '#e2dee6',
  526. colors: ['#ec5e44', '#f38259', '#f9a66d', '#98b480', '#57be8c'],
  527. },
  528. // used as a gradient,
  529. businessIconColors: ['#EA5BC2', '#6148CE'],
  530. demo: {
  531. headerSize: '70px',
  532. },
  533. };
  534. const darkAliases = {
  535. ...lightAliases,
  536. bodyBackground: colors.black,
  537. headerBackground: colors.gray500,
  538. background: colors.black,
  539. backgroundSecondary: colors.gray500,
  540. border: colors.gray400,
  541. innerBorder: colors.gray500,
  542. modalBorder: `1px solid ${colors.gray400}`,
  543. modalBoxShadow: '0 15px 40px 0 rgb(67 62 75 / 30%), 0 1px 15px 0 rgb(67 61 74 / 15%)',
  544. textColor: colors.white,
  545. subText: colors.gray200,
  546. linkColor: colors.blue200,
  547. linkHoverColor: colors.blue300,
  548. disabled: colors.gray400,
  549. active: colors.pink300,
  550. focus: colors.gray500,
  551. inactive: colors.gray200,
  552. error: colors.red300,
  553. success: colors.green300,
  554. primaryButtonBorder: colors.purple200,
  555. primaryButtonBorderActive: colors.purple200,
  556. secondaryButtonText: colors.purple200,
  557. secondaryButtonBorder: colors.purple200,
  558. sidebarGradient: 'linear-gradient(6.01deg, #0A090F -8.44%, #1B0921 85.02%)',
  559. formPlaceholder: colors.gray400,
  560. formText: colors.white,
  561. formInputBorder: colors.gray400,
  562. rowBackground: colors.gray500,
  563. chartLineColor: colors.gray500,
  564. chartLabel: colors.gray400,
  565. progressBar: colors.purple200,
  566. progressBackground: colors.gray400,
  567. badgeBackground: colors.gray400,
  568. alertBackgroundAlpha: 0.1,
  569. overlayBackgroundAlpha: 'rgba(18, 9, 23, 0.7)',
  570. tagBarHover: colors.purple300,
  571. tagBar: colors.gray400,
  572. businessIconColors: [colors.pink100, colors.pink300],
  573. badgeText: colors.black,
  574. searchTokenBackground: {
  575. valid: '#1F1A3D',
  576. validActive: color('#1F1A3D').lighten(0.05).string(),
  577. invalid: color(colors.red300).darken(0.8).string(),
  578. invalidActive: color(colors.red300).darken(0.7).string(),
  579. },
  580. searchTokenBorder: {
  581. valid: '#554E80',
  582. validActive: color('#554E80').lighten(0.15).string(),
  583. invalid: color(colors.red300).darken(0.5).string(),
  584. invalidActive: color(colors.red300).darken(0.4).string(),
  585. },
  586. buttonCountActive: colors.gray100,
  587. buttonCount: colors.gray400,
  588. bannerBackground: colors.purple100,
  589. };
  590. export const lightTheme = {
  591. ...commonTheme,
  592. ...lightAliases,
  593. alert: generateAlertTheme(lightAliases),
  594. badge: generateBadgeTheme(lightAliases),
  595. button: generateButtonTheme(lightAliases),
  596. };
  597. export const darkTheme: Theme = {
  598. ...commonTheme,
  599. ...darkAliases,
  600. alert: generateAlertTheme(darkAliases),
  601. badge: generateBadgeTheme(darkAliases),
  602. button: generateButtonTheme(darkAliases),
  603. };
  604. export type Theme = typeof lightTheme;
  605. export type Aliases = typeof lightAliases;
  606. export type Color = keyof typeof colors;
  607. export type IconSize = keyof typeof iconSizes;
  608. export default commonTheme;
  609. type MyTheme = Theme;
  610. /**
  611. * Configure Emotion to use our theme
  612. */
  613. declare module '@emotion/react' {
  614. // eslint-disable-next-line @typescript-eslint/no-shadow
  615. export interface Theme extends MyTheme {}
  616. }
  617. // This should never be used directly (except in storybook)
  618. export {lightAliases as aliases};