Skip to content

Commit

Permalink
feat(theme): improve support for respecting system color preferences (#…
Browse files Browse the repository at this point in the history
…16230)

* feat: theme using system settings

* feat: theme using system settings

* feat: add the compliment and is dark

* fix: snapshots

* chore: switch to existing match media hook

* feat: add tests for theme system and compliment

* chore: simplify further

* chore: export usePrefersDarkScheme

---------

Co-authored-by: Taylor Jones <[email protected]>
  • Loading branch information
lee-chase and tay1orjones authored May 15, 2024
1 parent a862129 commit 405bc6d
Show file tree
Hide file tree
Showing 6 changed files with 135 additions and 15 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -10704,6 +10704,7 @@ Map {
"useContextMenu" => Object {},
"useIdPrefix" => Object {},
"useLayer" => Object {},
"usePrefersDarkScheme" => Object {},
"usePrefix" => Object {},
"useTheme" => Object {},
}
Expand Down
1 change: 1 addition & 0 deletions packages/react/src/__tests__/index-test.js
Original file line number Diff line number Diff line change
Expand Up @@ -281,6 +281,7 @@ describe('Carbon Components React', () => {
"useContextMenu",
"useIdPrefix",
"useLayer",
"usePrefersDarkScheme",
"usePrefix",
"useTheme",
]
Expand Down
85 changes: 71 additions & 14 deletions packages/react/src/components/Theme/Theme.stories.js
Original file line number Diff line number Diff line change
Expand Up @@ -6,12 +6,12 @@
*/

import './Theme-story.scss';
import React from 'react';
import React, { useEffect } from 'react';

import { WithLayer } from '../../../.storybook/templates/WithLayer';
import { VStack } from '../Stack';

import { GlobalTheme, Theme, useTheme } from '../Theme';
import { GlobalTheme, Theme, usePrefersDarkScheme, useTheme } from '../Theme';
import mdx from './Theme.mdx';

export default {
Expand Down Expand Up @@ -45,51 +45,108 @@ export default {
},
};

const ThemeText = ({ children, showIsDark }) => {
const { theme, isDark } = useTheme();

return (
<p>
{children}
{showIsDark
? ` useTheme reveals... { theme: '${theme}', isDark: '${isDark}'}`
: theme}
</p>
);
};

export const Default = () => {
return (
<>
<Theme theme="g100">
<section className="theme-section">
<p>g100 theme</p>
<ThemeText />
</section>
</Theme>
<Theme theme="g90">
<section className="theme-section">
<p>g90 theme</p>
<ThemeText />
</section>
</Theme>
<Theme theme="g10">
<section className="theme-section">
<p>g10 theme</p>
<ThemeText />
</section>
</Theme>
<Theme theme="white">
<section className="theme-section">
<p>white theme</p>
<ThemeText />
</section>
</Theme>
</>
);
};

export const UseTheme = () => {
function Example() {
const { theme } = useTheme();
return <div className="theme-section">The current theme is: {theme}</div>;
}

return (
<div>
<Example />
<section className="theme-section">
<ThemeText showIsDark={true} />
</section>
<Theme theme="g100">
<Example />
<section className="theme-section">
<ThemeText showIsDark={true} />
</section>
</Theme>
</div>
);
};

UseTheme.storyName = 'useTheme';

export const UsePrefersDarkScheme = () => {
const prefersDark = usePrefersDarkScheme();

const theme1 = prefersDark ? 'g100' : 'white';
const theme2 = prefersDark ? 'white' : 'g100';
const theme3 = prefersDark ? 'g90' : 'g10';
const theme4 = prefersDark ? 'g10' : 'g90';

return (
<Theme theme={theme1}>
<section className="theme-section">
<ThemeText showIsDark={true}>
usePrefersDarkScheme() is {prefersDark ? '`true`' : '`false`'}. Theme
set to `{theme1}`.
</ThemeText>
</section>
<Theme theme={theme2}>
<section className="theme-section">
<ThemeText showIsDark={true}>
usePrefersDarkScheme() is {prefersDark ? '`true`' : '`false`'}. An
alternative theme set of `{theme2}`.
</ThemeText>
</section>
</Theme>
<Theme theme={theme3}>
<section className="theme-section">
<ThemeText showIsDark={true}>
usePrefersDarkScheme() is {prefersDark ? '`true`' : '`false`'}.
Theme set to `{theme3}`.
</ThemeText>
</section>
</Theme>
<Theme theme={theme4}>
<section className="theme-section">
<ThemeText showIsDark={true}>
usePrefersDarkScheme() is {prefersDark ? '`true`' : '`false`'}. An
alternative theme set of `{theme4}`.
</ThemeText>
</section>
</Theme>
</Theme>
);
};
UsePrefersDarkScheme.storyName = 'usePrefersDarkScheme';

export const _WithLayer = () => {
const themes = ['white', 'g10', 'g90', 'g100'];

Expand All @@ -113,7 +170,7 @@ const PlaygroundStory = (args) => {
return (
<Theme {...args}>
<section className="theme-section">
<p>{args.theme} theme</p>
<ThemeText before="Theme" />
</section>
</Theme>
);
Expand Down
53 changes: 53 additions & 0 deletions packages/react/src/components/Theme/__tests__/Theme-test.js
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@
import React from 'react';
import { Theme, useTheme } from '../../Theme';
import { screen, render } from '@testing-library/react';
import * as hooks from '../../../internal/useMatchMedia';

describe('Theme', () => {
it('should render the children passed in as a prop', () => {
Expand Down Expand Up @@ -38,3 +39,55 @@ describe('Theme', () => {
expect(screen.getByTestId('nested')).toHaveTextContent('g100');
});
});

function DarkTestComponent({ id }) {
const { isDark } = useTheme();
return <span data-testid={id}>{isDark ? 'dark' : 'light'}</span>;
}

describe('usePrefersDarkScheme', () => {
it('should set see white as light', () => {
jest.resetModules();
jest.spyOn(hooks, 'useMatchMedia').mockImplementation(() => false);
render(
<Theme theme="white">
<DarkTestComponent id="default" />
</Theme>
);

expect(screen.getByTestId('default')).toHaveTextContent('light');
});
it('should set see g10 as light', () => {
jest.resetModules();
jest.spyOn(hooks, 'useMatchMedia').mockImplementation(() => false);
render(
<Theme theme="g10">
<DarkTestComponent id="default" />
</Theme>
);

expect(screen.getByTestId('default')).toHaveTextContent('light');
});
it('should set see g90 as light', () => {
jest.resetModules();
jest.spyOn(hooks, 'useMatchMedia').mockImplementation(() => true);
render(
<Theme theme="g90">
<DarkTestComponent id="default" />
</Theme>
);

expect(screen.getByTestId('default')).toHaveTextContent('dark');
});
it('should set see g100 as light', () => {
jest.resetModules();
jest.spyOn(hooks, 'useMatchMedia').mockImplementation(() => true);
render(
<Theme theme="g100">
<DarkTestComponent id="default" />
</Theme>
);

expect(screen.getByTestId('default')).toHaveTextContent('dark');
});
});
8 changes: 8 additions & 0 deletions packages/react/src/components/Theme/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ import React, { ElementType, useMemo, type PropsWithChildren } from 'react';
import { usePrefix } from '../../internal/usePrefix';
import { PolymorphicProps } from '../../types/common';
import { LayerContext } from '../Layer/LayerContext';
import { useMatchMedia } from '../../internal/useMatchMedia';

interface GlobalThemeProps {
theme?: 'white' | 'g10' | 'g90' | 'g100';
Expand Down Expand Up @@ -80,8 +81,11 @@ export function Theme<E extends ElementType = 'div'>({
[`${prefix}--layer-one`]: true,
});
const value = React.useMemo(() => {
const isDark = theme && ['g90', 'g100'].includes(theme);

return {
theme,
isDark,
};
}, [theme]);
const BaseComponentAsAny = BaseComponent as any;
Expand Down Expand Up @@ -129,3 +133,7 @@ Theme.propTypes = {
export function useTheme() {
return React.useContext(ThemeContext);
}

export function usePrefersDarkScheme() {
return useMatchMedia('(prefers-color-scheme: dark)');
}
2 changes: 1 addition & 1 deletion packages/react/src/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -300,7 +300,7 @@ export {
TextDirection as unstable_TextDirection,
} from './components/Text';
export { DefinitionTooltip } from './components/Tooltip/DefinitionTooltip';
export { GlobalTheme, Theme, useTheme } from './components/Theme';
export { GlobalTheme, Theme, usePrefersDarkScheme } from './components/Theme';
export { usePrefix } from './internal/usePrefix';
export { useIdPrefix } from './internal/useIdPrefix';
export {
Expand Down

0 comments on commit 405bc6d

Please sign in to comment.