sample.tsx 3.2 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135
  1. import {createContext, useState} from 'react';
  2. import {ThemeProvider, useTheme} from '@emotion/react';
  3. import styled from '@emotion/styled';
  4. import {IconMoon} from 'sentry/icons';
  5. import space from 'sentry/styles/space';
  6. import {darkTheme, lightTheme, Theme} from 'sentry/utils/theme';
  7. type ThemeName = 'dark' | 'light';
  8. type Props = {
  9. children?: React.ReactChild;
  10. /**
  11. * Show the theme switcher, which allows for
  12. * switching the local theme context between
  13. * light and dark mode. Useful for previewing
  14. * components in both modes.
  15. */
  16. showThemeSwitcher?: boolean;
  17. /**
  18. * Remove the outer border and padding
  19. */
  20. noBorder?: boolean;
  21. };
  22. /**
  23. * Expose the selected theme to children of <Sample />
  24. */
  25. export const SampleThemeContext = createContext<ThemeName>('light');
  26. const Sample = ({children, showThemeSwitcher = false, noBorder = false}: Props) => {
  27. const [themeName, setThemeName] = useState<ThemeName>('light');
  28. /**
  29. * If theme switcher is shown, use the correct theme object based on themeName.
  30. * Else, fall back to the global theme object.
  31. */
  32. const [theme, setTheme] = useState<Theme>(
  33. showThemeSwitcher ? (themeName === 'light' ? lightTheme : darkTheme) : useTheme()
  34. );
  35. const toggleTheme = () => {
  36. if (themeName === 'light') {
  37. setThemeName('dark');
  38. setTheme(darkTheme);
  39. } else {
  40. setThemeName('light');
  41. setTheme(lightTheme);
  42. }
  43. };
  44. return (
  45. <Wrap>
  46. {showThemeSwitcher && (
  47. <ThemeSwitcher onClick={toggleTheme} active={themeName === 'dark'}>
  48. <IconMoon />
  49. </ThemeSwitcher>
  50. )}
  51. <ThemeProvider theme={theme}>
  52. <InnerWrap noBorder={noBorder} addTopMargin={showThemeSwitcher}>
  53. <SampleThemeContext.Provider value={themeName}>
  54. {children}
  55. </SampleThemeContext.Provider>
  56. </InnerWrap>
  57. </ThemeProvider>
  58. </Wrap>
  59. );
  60. };
  61. export default Sample;
  62. const Wrap = styled('div')`
  63. position: relative;
  64. `;
  65. const InnerWrap = styled('div')<{noBorder: boolean; addTopMargin: boolean}>`
  66. position: relative;
  67. border-radius: ${p => p.theme.borderRadius};
  68. margin: ${space(2)} 0;
  69. color: ${p => p.theme.textColor};
  70. ${p =>
  71. !p.noBorder &&
  72. `
  73. border: solid 1px ${p.theme.border};
  74. background: ${p.theme.background};
  75. padding: ${space(2)} ${space(2)};
  76. `}
  77. ${p => p.addTopMargin && `margin-top: calc(${space(4)} + ${space(2)});`}
  78. & > *:first-of-type {
  79. margin-top: 0;
  80. }
  81. & > *:last-of-type {
  82. margin-bottom: 0;
  83. }
  84. /* Overwrite text color that was set in previewGlobalStyles.tsx */
  85. div,
  86. p,
  87. a,
  88. button {
  89. color: ${p => p.theme.textColor};
  90. }
  91. `;
  92. const ThemeSwitcher = styled('button')<{active: boolean}>`
  93. position: absolute;
  94. top: 0;
  95. right: ${space(0.5)};
  96. transform: translateY(calc(-100% - ${space(0.5)}));
  97. border: none;
  98. border-radius: ${p => p.theme.borderRadius};
  99. background: transparent;
  100. display: flex;
  101. align-items: center;
  102. padding: ${space(1)};
  103. margin-bottom: ${space(0.5)};
  104. color: ${p => p.theme.subText};
  105. &:hover {
  106. background: ${p => p.theme.innerBorder};
  107. color: ${p => p.theme.textColor};
  108. }
  109. ${p =>
  110. p.active &&
  111. `&, &:hover {
  112. color: ${p.theme.textColor};
  113. }
  114. `}
  115. `;