sample.tsx 3.3 KB

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