Skip to content

Commit

Permalink
feat: add theme switcher to eui+ docs
Browse files Browse the repository at this point in the history
  • Loading branch information
mgadewoll committed Sep 19, 2024
1 parent 4b09a49 commit 03a0136
Show file tree
Hide file tree
Showing 8 changed files with 177 additions and 28 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -91,9 +91,9 @@ export const NavbarItem = (props: Props) => {
} = props;

const isBrowser = useIsBrowser();
const { theme } = useContext(AppThemeContext);
const { colorMode } = useContext(AppThemeContext);

const isDarkMode = theme === 'dark';
const isDarkMode = colorMode === 'dark';

const styles = useEuiMemoizedStyles(getStyles);
const cssStyles = [
Expand Down
58 changes: 47 additions & 11 deletions packages/docusaurus-theme/src/components/theme_context/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -5,17 +5,39 @@ import {
useState,
} from 'react';
import useIsBrowser from '@docusaurus/useIsBrowser';
import { EUI_THEMES, EuiProvider, EuiThemeColorMode } from '@elastic/eui';
import {
EuiProvider,
EuiThemeAmsterdam,
EuiThemeColorMode,
} from '@elastic/eui';
import { EuiThemeBerlin } from '@elastic/eui-theme-berlin';

import { EuiThemeOverrides } from './theme_overrides';

const EUI_THEME_NAMES = EUI_THEMES.map(
({ value }) => value
) as EuiThemeColorMode[];
const EXPERIMENTAL_THEMES = [
{
text: 'Berlin',
value: EuiThemeBerlin.key,
provider: EuiThemeBerlin,
},
];

export const AVAILABLE_THEMES = [
{
text: 'Amsterdam',
value: EuiThemeAmsterdam.key,
provider: EuiThemeAmsterdam,
},
...EXPERIMENTAL_THEMES,
];

const EUI_COLOR_MODES = ['light', 'dark'] as EuiThemeColorMode[];

const defaultState = {
theme: EUI_THEME_NAMES[0] as EuiThemeColorMode,
changeTheme: (themeValue: EuiThemeColorMode) => {},
colorMode: EUI_COLOR_MODES[0] as EuiThemeColorMode,
changeColorMode: (colorMode: EuiThemeColorMode) => {},
theme: AVAILABLE_THEMES[0]!,
changeTheme: (themeValue: string) => {},
};

export const AppThemeContext = createContext(defaultState);
Expand All @@ -24,25 +46,39 @@ export const AppThemeProvider: FunctionComponent<PropsWithChildren> = ({
children,
}) => {
const isBrowser = useIsBrowser();
const [theme, setTheme] = useState<EuiThemeColorMode>(() => {
const [colorMode, setColorMode] = useState<EuiThemeColorMode>(() => {
if (isBrowser) {
return localStorage.getItem('theme') as EuiThemeColorMode ?? defaultState.theme;
return (
(localStorage.getItem('theme') as EuiThemeColorMode) ??
defaultState.colorMode
);
}

return defaultState.theme;
return defaultState.colorMode;
});

const [theme, setTheme] = useState(defaultState.theme);

const handleChangeTheme = (themeValue: string) => {
const themeObj = AVAILABLE_THEMES.find((t) => t.value === themeValue);

setTheme((currentTheme) => themeObj ?? currentTheme);
};

return (
<AppThemeContext.Provider
value={{
colorMode,
theme,
changeTheme: setTheme,
changeColorMode: setColorMode,
changeTheme: handleChangeTheme,
}}
>
<EuiProvider
globalStyles={false}
modify={EuiThemeOverrides}
colorMode={theme}
colorMode={colorMode}
theme={theme.provider}
>
{children}
</EuiProvider>
Expand Down
94 changes: 94 additions & 0 deletions packages/docusaurus-theme/src/components/theme_switcher/index.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,94 @@
import { useContext, useEffect, useState } from 'react';
import { css } from '@emotion/react';
import {
EuiAvatar,
EuiButtonEmpty,
euiFocusRing,
EuiListGroup,
EuiListGroupItem,
EuiPopover,
useEuiMemoizedStyles,
useEuiTheme,
UseEuiTheme,
} from '@elastic/eui';

import { AppThemeContext, AVAILABLE_THEMES } from '../theme_context';

const getStyles = (euiThemeContext: UseEuiTheme) => {
const { euiTheme } = euiThemeContext;

return {
button: css`
padding: 0;
`,
listItem: css`
.euiListGroupItem__button:focus-visible {
// overriding the global "outset" style to ensure the focus style is not cut off
${euiFocusRing(euiThemeContext, 'inset', {
color: euiTheme.colors.primary,
})};
}
`,
};
};

export const ThemeSwitcher = () => {
const { euiTheme } = useEuiTheme();
const [currentTheme, setCurrentTheme] = useState(
AVAILABLE_THEMES[0]?.value ?? ''
);
const [isPopoverOpen, setPopoverOpen] = useState(false);
const { theme, changeTheme } = useContext(AppThemeContext);

useEffect(() => {
changeTheme(currentTheme);
}, [currentTheme]);

const styles = useEuiMemoizedStyles(getStyles);

const button = (
<EuiButtonEmpty
size="s"
color="text"
css={styles.button}
onClick={() => setPopoverOpen((isOpen) => !isOpen)}
aria-label={`${theme.text} theme`}
>
<EuiAvatar name={theme.text} size="s" color={euiTheme.colors.primary} />
</EuiButtonEmpty>
);

return (
<EuiPopover
isOpen={isPopoverOpen}
closePopover={() => setPopoverOpen(false)}
button={button}
panelPaddingSize="xs"
repositionOnScroll
aria-label="EUI theme list"
>
<EuiListGroup>
{AVAILABLE_THEMES &&
AVAILABLE_THEMES.map((theme) => {
const isCurrentTheme = currentTheme === theme.value;

const handleOnClick = () => {
setCurrentTheme(theme.value);
};

return (
<EuiListGroupItem
key={theme.value}
css={styles.listItem}
label={theme.text}
size="xs"
isActive={isCurrentTheme}
color={isCurrentTheme ? 'primary' : 'text'}
onClick={handleOnClick}
/>
);
})}
</EuiListGroup>
</EuiPopover>
);
};
12 changes: 6 additions & 6 deletions packages/docusaurus-theme/src/theme/ColorModeToggle/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -17,20 +17,20 @@ function ColorModeToggle({
onChange,
...rest
}: WrappedProps): JSX.Element {
const { theme, changeTheme } = useContext(AppThemeContext);
const { colorMode, changeColorMode } = useContext(AppThemeContext);

useEffect(() => {
changeTheme(value);
changeColorMode(value);
}, []);

const handleOnChange = (themeName: EuiThemeColorMode) => {
changeTheme(themeName);
onChange?.(themeName);
const handleOnChange = (colorMode: EuiThemeColorMode) => {
changeColorMode(colorMode);
onChange?.(colorMode);
};

return (
<OriginalColorModeToggle
value={theme}
value={colorMode}
onChange={handleOnChange}
{...rest}
/>
Expand Down
4 changes: 2 additions & 2 deletions packages/docusaurus-theme/src/theme/Logo/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -62,8 +62,8 @@ function LogoThemedImage({
alt: string;
imageClassName?: string;
}) {
const { theme } = useContext(AppThemeContext);
const isDarkMode = theme === 'dark';
const { colorMode } = useContext(AppThemeContext);
const isDarkMode = colorMode === 'dark';

const styles = useEuiMemoizedStyles(getStyles);

Expand Down
24 changes: 23 additions & 1 deletion packages/docusaurus-theme/src/theme/Navbar/Content/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,13 @@ import SearchBar from '@theme-original/SearchBar';
import NavbarMobileSidebarToggle from '@theme-original/Navbar/MobileSidebar/Toggle';
import NavbarLogo from '@theme-original/Navbar/Logo';
import NavbarSearch from '@theme-original/Navbar/Search';
import { euiFocusRing, useEuiMemoizedStyles, UseEuiTheme } from '@elastic/eui';
import {
euiFocusRing,
euiTextTruncate,
useEuiMemoizedStyles,
UseEuiTheme,
isExperimentalThemeEnabled,
} from '@elastic/eui';
import {
euiFormControlText,
euiFormVariables,
Expand All @@ -24,6 +30,7 @@ import {
import euiVersions from '@site/static/versions.json';

import { VersionSwitcher } from '../../../components/version_switcher';
import { ThemeSwitcher } from '../../../components/theme_switcher';

const DOCS_PATH = '/docs';

Expand Down Expand Up @@ -67,6 +74,10 @@ const getStyles = (euiThemeContext: UseEuiTheme) => {
@media (min-width: 997px) {
gap: ${euiTheme.size.l};
}
.navbar__link {
${euiTextTruncate()}
}
`,
navbarItemsRight: css`
gap: ${euiTheme.size.s};
Expand Down Expand Up @@ -125,6 +136,11 @@ const getStyles = (euiThemeContext: UseEuiTheme) => {
display: none;
}
`,
themeSwitcher: css`
@media (max-width: 996px) {
display: none;
}
`,
};
};

Expand Down Expand Up @@ -218,6 +234,12 @@ export default function NavbarContent(): JSX.Element {
)}
<NavbarColorModeToggle className="colorModeToggle" />
<NavbarItems items={rightItems} />

{isBrowser && isExperimentalThemeEnabled() && (
<div css={styles.themeSwitcher}>
<ThemeSwitcher />
</div>
)}
</>
}
/>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,8 @@ import useIsBrowser from '@docusaurus/useIsBrowser';
import euiVersions from '@site/static/versions.json';

import { VersionSwitcher } from '../../../../components/version_switcher';
import { ThemeSwitcher } from '../../../../components/theme_switcher';
import { isExperimentalThemeEnabled } from '@elastic/eui/lib/themes/themes';

const getStyles = ({ euiTheme }: UseEuiTheme) => ({
sidebar: css`
Expand Down Expand Up @@ -64,6 +66,7 @@ export default function NavbarMobileSidebarHeader(): JSX.Element {
<div className="navbar-sidebar__brand" css={styles.sidebar}>
<NavbarLogo />
{isBrowser && versions && <VersionSwitcher versions={versions} />}
{isBrowser && isExperimentalThemeEnabled() && <ThemeSwitcher />}
<NavbarColorModeToggle />
<CloseButton />
</div>
Expand Down
6 changes: 0 additions & 6 deletions packages/website/src/components/homepage/highlights/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,6 @@ import {
EuiButtonEmpty,
EuiCard,
EuiIcon,
EuiImage,
EuiText,
EuiTextAlign,
useEuiMemoizedStyles,
Expand All @@ -12,8 +11,6 @@ import {
} from '@elastic/eui';
import { HomepageContainer, HomepageSection } from '../layout';
import { css } from '@emotion/react';
import { useContext } from 'react';
import { AppThemeContext } from '@elastic/eui-docusaurus-theme/lib/components/theme_context/index.js';

import SvgFlex from './svg/flex.svg';
import SvgSpacer from './svg/spacer.svg';
Expand Down Expand Up @@ -223,10 +220,7 @@ const getStyles = ({ euiTheme, colorMode }: UseEuiTheme) => ({
});

export const HomepageHighlights = () => {
const { theme } = useContext(AppThemeContext);
const styles = useEuiMemoizedStyles(getStyles);
const isDarkMode = theme === 'dark';

const headingId = useGeneratedHtmlId();

return (
Expand Down

0 comments on commit 03a0136

Please sign in to comment.