diff --git a/package.json b/package.json
index 769165d5e8f..a1fe7734333 100644
--- a/package.json
+++ b/package.json
@@ -59,8 +59,8 @@
"@babel/core": "7.13.10",
"@babel/preset-typescript": "^7.13.0",
"@emotion/babel-plugin": "^11.1.2",
- "@emotion/react": "^11.1.4",
- "@emotion/styled": "^11.0.0",
+ "@emotion/react": "^11.1.5",
+ "@emotion/styled": "^11.1.5",
"@testing-library/jest-dom": "^5.7.0",
"@testing-library/react": "^11.0.4",
"@testing-library/react-hooks": "^5.1.0",
diff --git a/packages/gamut-styles/package.json b/packages/gamut-styles/package.json
index 8ceabab534c..f5a35292641 100644
--- a/packages/gamut-styles/package.json
+++ b/packages/gamut-styles/package.json
@@ -21,14 +21,16 @@
"@codecademy/variance": "^0.6.0"
},
"peerDependencies": {
- "@emotion/react": "^11.1.4",
- "@emotion/styled": "^11.0.0",
+ "@emotion/react": "^11.15",
+ "@emotion/styled": "^11.1.5",
"react-helmet": "^6.1.0"
},
"devDependencies": {
- "@emotion/react": "^11.1.4",
- "@emotion/styled": "^11.0.0",
- "@types/react-helmet": "^6.1.0"
+ "@emotion/react": "^11.1.5",
+ "@emotion/styled": "^11.1.5",
+ "@emotion/jest": "11.2.1",
+ "@types/react-helmet": "^6.1.0",
+ "component-test-setup": "^0.1.1"
},
"license": "MIT",
"publishConfig": {
diff --git a/packages/gamut-styles/src/AssetProvider.tsx b/packages/gamut-styles/src/AssetProvider.tsx
index b51e8463304..76d7029743e 100644
--- a/packages/gamut-styles/src/AssetProvider.tsx
+++ b/packages/gamut-styles/src/AssetProvider.tsx
@@ -3,24 +3,19 @@ import { Helmet } from 'react-helmet';
import { webFonts } from './remoteAssets/fonts';
-export const createFontLinks = () => {
- const links: React.ReactNode[] = [];
- webFonts.forEach(({ filePath, extensions }) =>
- extensions.forEach((ext) =>
- links.push(
-
- )
- )
+export const createFontLinks = () =>
+ webFonts.flatMap(({ filePath, extensions }) =>
+ extensions.map((ext) => (
+
+ ))
);
- return links;
-};
export const AssetProvider = () => {
return {createFontLinks()};
diff --git a/packages/gamut-styles/src/Background.tsx b/packages/gamut-styles/src/Background.tsx
new file mode 100644
index 00000000000..feee16df54c
--- /dev/null
+++ b/packages/gamut-styles/src/Background.tsx
@@ -0,0 +1,67 @@
+import { useTheme } from '@emotion/react';
+import styled from '@emotion/styled';
+import { getContrast, meetsContrastGuidelines } from 'polished';
+import React, { useMemo } from 'react';
+
+import { ColorMode } from './ColorMode';
+import { properties } from './props';
+import { colors } from './variables';
+
+const Reset = styled.div(properties.backgroundColor);
+
+export type BackgroundProps = {
+ initialBackground: keyof typeof colors;
+ className?: string;
+};
+
+export const Background: React.FC = ({
+ children,
+ className,
+ initialBackground,
+}) => {
+ const {
+ colorModes: { active, modes },
+ } = useTheme();
+ const accessibleMode = useMemo(() => {
+ const { light, dark } = modes;
+
+ const lightModeContrast = getContrast(
+ light.text,
+ colors[initialBackground]
+ );
+ const darkModeContrast = getContrast(dark.text, colors[initialBackground]);
+
+ // Minimum Contrast Requirement is 4.5 for AA (this will not be a perfect metric since there are multiple standards but should meet most of our needs)
+ const highestContrastMode =
+ lightModeContrast > darkModeContrast ? 'light' : 'dark';
+ const { AA } = meetsContrastGuidelines(
+ modes[highestContrastMode].text,
+ colors[initialBackground]
+ );
+ if (!AA) {
+ // eslint-disable-next-line no-console
+ console.warn(
+ `You are using an inaccessible background color ${initialBackground} (${colors[initialBackground]}) for color contrast`
+ );
+ }
+ return highestContrastMode;
+ }, [initialBackground, modes]);
+
+ if (accessibleMode === active) {
+ return (
+
+ {children}
+
+ );
+ }
+
+ return (
+
+ {children}
+
+ );
+};
diff --git a/packages/gamut-styles/src/ColorMode.tsx b/packages/gamut-styles/src/ColorMode.tsx
new file mode 100644
index 00000000000..0fd4b362c9f
--- /dev/null
+++ b/packages/gamut-styles/src/ColorMode.tsx
@@ -0,0 +1,47 @@
+import { compose, HandlerProps } from '@codecademy/gamut-system';
+import { Theme, ThemeProvider, useTheme } from '@emotion/react';
+import styled from '@emotion/styled';
+import React from 'react';
+
+import { properties } from './props';
+import { createVariables } from './utilities';
+import { colors } from './variables';
+
+export type ColorModeProps = {
+ mode: keyof Theme['colorModes']['modes'];
+ initialBackground?: keyof typeof colors;
+ className?: string;
+};
+
+const colorProps = compose(properties.backgroundColor, properties.textColor);
+
+export interface VariableProviderProps extends HandlerProps {
+ variables: Parameters[0];
+}
+
+export const VariableProvider = styled.div(
+ compose(properties.backgroundColor, properties.textColor),
+ ({ variables }) => createVariables(variables, 'colors')
+);
+
+export const ColorMode: React.FC = ({
+ mode,
+ initialBackground,
+ children,
+ className,
+}) => {
+ const { colorModes } = useTheme();
+
+ return (
+
+
+ {children}
+
+
+ );
+};
diff --git a/packages/gamut-styles/src/GamutProvider.tsx b/packages/gamut-styles/src/GamutProvider.tsx
index 4434524a071..760e6b16168 100644
--- a/packages/gamut-styles/src/GamutProvider.tsx
+++ b/packages/gamut-styles/src/GamutProvider.tsx
@@ -1,19 +1,23 @@
import {
CacheProvider,
css,
+ CSSObject,
EmotionCache,
Global,
+ Theme,
ThemeProvider,
} from '@emotion/react';
-import React, { useContext, useRef } from 'react';
+import React, { useContext, useMemo, useRef } from 'react';
import { createEmotionCache } from './cache';
import { Reboot, Typography } from './globals';
import { theme, themeCssVariables } from './theme';
+import { createVariables } from './utilities';
export interface GamutProviderProps {
useGlobals?: boolean;
useCache?: boolean;
+ mode?: keyof Theme['colorModes']['modes'];
cache?: EmotionCache;
}
@@ -27,12 +31,25 @@ GamutContext.displayName = 'GamutContext';
export const GamutProvider: React.FC = ({
children,
cache,
+ mode = 'light',
useGlobals = true,
useCache = true,
}) => {
const { hasGlobals, hasCache } = useContext(GamutContext);
const shouldCreateCache = useCache && !hasCache;
const shouldInsertGlobals = useGlobals && !hasGlobals;
+ const rootVariables = useMemo(() => {
+ const vars: CSSObject = {
+ ...themeCssVariables,
+ ...createVariables(theme.colorModes.modes[mode], 'colors'),
+ };
+ return vars;
+ }, [mode]);
+
+ const rootTheme = useMemo(
+ () => ({ ...theme, colorModes: { ...theme.colorModes, active: mode } }),
+ [mode]
+ );
// Do not initialize a new cache if one has been provided as props
const activeCache = useRef(
@@ -43,7 +60,7 @@ export const GamutProvider: React.FC = ({
<>
-
+
>
);
@@ -57,7 +74,7 @@ export const GamutProvider: React.FC = ({
>
{globals}
- {children}
+ {children}
);
@@ -71,7 +88,7 @@ export const GamutProvider: React.FC = ({
}}
>
{globals}
- {children}
+ {children}
);
};
diff --git a/packages/gamut-styles/src/__tests__/Background-test.tsx b/packages/gamut-styles/src/__tests__/Background-test.tsx
new file mode 100644
index 00000000000..97d39010655
--- /dev/null
+++ b/packages/gamut-styles/src/__tests__/Background-test.tsx
@@ -0,0 +1,105 @@
+import { matchers } from '@emotion/jest';
+import { ThemeProvider, useTheme } from '@emotion/react';
+import { setupRtl as setupRtlBase } from 'component-test-setup';
+import { overArgs } from 'lodash';
+import React from 'react';
+
+import { Background } from '../Background';
+import { theme } from '../theme';
+
+expect.extend(matchers);
+
+function withThemeProvider(
+ WrappedComponent: React.ComponentType
+) {
+ const WithBoundaryComponent: React.FC = (props) => (
+
+
+
+ );
+
+ return WithBoundaryComponent;
+}
+
+const setupRtl = overArgs(
+ setupRtlBase,
+ withThemeProvider
+) as typeof setupRtlBase;
+
+const renderView = setupRtl(Background, {
+ children: ,
+});
+
+const ActiveMode = () => {
+ const {
+ colorModes: { active },
+ } = useTheme();
+ return {active}
;
+};
+
+describe('Background', () => {
+ it('switches the default colormode when contrast standards are not met', () => {
+ const { view } = renderView({ initialBackground: 'navy' });
+ expect(view.getByTestId('content').parentElement).toHaveStyleRule(
+ 'background-color',
+ theme.colors.navy
+ );
+ });
+
+ it('allows for changing the color mode while nested', () => {
+ const { view } = renderView({
+ initialBackground: 'navy',
+ children: (
+
+ ),
+ });
+ expect(view.getByTestId('content').parentElement).toHaveStyleRule(
+ 'background-color',
+ theme.colors.navy
+ );
+
+ expect(view.getByTestId('nested-content').parentElement).toHaveStyleRule(
+ 'background-color',
+ theme.colors.beige
+ );
+
+ /** text color reset should be on the variable provisioner if needed */
+ expect(view.getByTestId('nested-content').parentElement).toHaveStyleRule(
+ 'color',
+ theme.colors.text
+ );
+ });
+
+ it('does not change the color mode when contrasts do not conflict', () => {
+ const { view } = renderView({
+ initialBackground: 'white',
+ });
+
+ // Grand parent
+ expect(
+ view.getByTestId('content').parentElement?.parentElement
+ ).not.toHaveStyleRule('color', theme.colors.text);
+ });
+
+ it('updates the theme context to the current mode', () => {
+ const { view } = renderView({
+ initialBackground: 'navy',
+ children: ,
+ });
+
+ view.getByText('dark');
+ });
+
+ it('does not update the theme context when the color mode has not changed', () => {
+ const { view } = renderView({
+ initialBackground: 'white',
+ children: ,
+ });
+
+ view.getByText('light');
+ });
+});
diff --git a/packages/gamut-styles/src/index.ts b/packages/gamut-styles/src/index.ts
index 62fddd79422..0e0eb287ef4 100644
--- a/packages/gamut-styles/src/index.ts
+++ b/packages/gamut-styles/src/index.ts
@@ -3,6 +3,8 @@ import '@emotion/react';
import { theme } from './theme';
export * from './GamutProvider';
+export * from './ColorMode';
+export * from './Background';
export * from './cache';
export * from './variables';
diff --git a/packages/gamut-styles/src/theme.ts b/packages/gamut-styles/src/theme.ts
index 5619bf7e5b8..5335be2b51d 100644
--- a/packages/gamut-styles/src/theme.ts
+++ b/packages/gamut-styles/src/theme.ts
@@ -1,6 +1,21 @@
import { createThemeVariables } from './utilities';
import * as tokens from './variables';
+const themeColors = {
+ ...tokens.colors,
+ ...tokens.colorModes.light,
+};
+
+export interface ColorModes>> {
+ active: keyof T;
+ modes: T;
+}
+
+const colorModes: ColorModes = {
+ active: 'light',
+ modes: tokens.colorModes,
+};
+
export const baseTheme = {
boxShadows: tokens.boxShadows,
breakpoints: tokens.mediaQueries,
@@ -8,12 +23,13 @@ export const baseTheme = {
fontFamily: tokens.fontFamily,
lineHeight: tokens.lineHeight,
fontWeight: tokens.fontWeight,
- colors: tokens.colors,
+ colors: themeColors,
spacing: tokens.spacing,
elements: tokens.elements,
+ colorModes,
} as const;
export const {
theme,
cssVariables: themeCssVariables,
-} = createThemeVariables(baseTheme, ['elements']);
+} = createThemeVariables(baseTheme, ['elements', 'colors']);
diff --git a/packages/gamut-styles/src/utilities/createThemeVariables.ts b/packages/gamut-styles/src/utilities/createThemeVariables.ts
index 4575b472edd..3b465a99531 100644
--- a/packages/gamut-styles/src/utilities/createThemeVariables.ts
+++ b/packages/gamut-styles/src/utilities/createThemeVariables.ts
@@ -7,8 +7,11 @@ import { createVariables } from './createVariables';
/**
* Returns an type of any object with { key: 'var(--key) }
*/
-export type KeyAsVariable> = {
- [V in keyof T]: `var(--${Extract})`;
+export type KeyAsVariable<
+ T extends Record,
+ Prefix extends string
+> = {
+ [V in keyof T]: `var(--${Prefix}-${Extract})`;
};
/**
@@ -19,7 +22,7 @@ export type ThemeWithVariables<
VariableKeys extends (keyof Theme)[]
> = {
[Key in keyof Theme]: Key extends VariableKeys[number]
- ? KeyAsVariable
+ ? KeyAsVariable
: Theme[Key];
};
@@ -50,7 +53,7 @@ export const createThemeVariables: CreateThemeVars = (theme, keys) => {
// Update the theme object with the new tokens
for (const variable in tokensToSerialize) {
if (hasIn(tokensToSerialize, variable)) {
- const variableReference = `var(--${variable})`;
+ const variableReference = `var(--${key}-${variable})`;
updatedTheme[key][variable] = variableReference;
}
}
@@ -68,7 +71,7 @@ export const createThemeVariables: CreateThemeVars = (theme, keys) => {
});
// Create the variables and merge with the rest of the vars
- merge(cssVariables, createVariables(replacedBreakpointAliases));
+ merge(cssVariables, createVariables(replacedBreakpointAliases, key));
});
return { cssVariables, theme: updatedTheme };
diff --git a/packages/gamut-styles/src/utilities/createVariables.ts b/packages/gamut-styles/src/utilities/createVariables.ts
index e4c85e143aa..5a84a59275a 100644
--- a/packages/gamut-styles/src/utilities/createVariables.ts
+++ b/packages/gamut-styles/src/utilities/createVariables.ts
@@ -2,14 +2,15 @@ import { CSSObject } from '@emotion/react';
import { hasIn, merge } from 'lodash';
export const createVariables = (
- tokens: Record
+ tokens: Record,
+ prefix: string | symbol | number
) => {
const cssVariables: CSSObject = {};
for (const variable in tokens) {
if (!hasIn(tokens, variable)) continue;
- const varName = `--${variable}`;
+ const varName = `--${String(prefix)}-${variable}`;
const valuesToRegister = tokens[variable];
// For all variables in the theme scale add theme to the resulting CSS Object
diff --git a/packages/gamut-styles/src/variables/colors.ts b/packages/gamut-styles/src/variables/colors.ts
index 524b1b1f377..3b4084353b3 100644
--- a/packages/gamut-styles/src/variables/colors.ts
+++ b/packages/gamut-styles/src/variables/colors.ts
@@ -167,3 +167,25 @@ export const platformColors = {
'900': '#15141f',
},
} as const;
+
+export const shroudColor = {
+ dark: 'rgba(0,0,0, .75)',
+ light: 'rgba(255, 255, 255, 0.95)',
+} as const;
+
+export const colorModes = {
+ light: {
+ background: colors.white,
+ text: colors.navy,
+ primary: colors.hyper,
+ secondary: colors.navy,
+ shadow: shroudColor.light,
+ },
+ dark: {
+ background: colors.navy,
+ text: colors.white,
+ primary: colors.yellow,
+ secondary: colors.white,
+ shadow: shroudColor.dark,
+ },
+};
diff --git a/packages/gamut-system/package.json b/packages/gamut-system/package.json
index 33344934c4e..9bb7d3ed0da 100644
--- a/packages/gamut-system/package.json
+++ b/packages/gamut-system/package.json
@@ -23,8 +23,8 @@
},
"devDependencies": {
"@emotion/jest": "^11.1.0",
- "@emotion/react": "^11.1.4",
- "@emotion/styled": "^11.0.0"
+ "@emotion/react": "^11.1.5",
+ "@emotion/styled": "^11.1.5"
},
"license": "MIT"
}
diff --git a/packages/gamut/package.json b/packages/gamut/package.json
index 58f1192e1ee..7ce9514c3fb 100644
--- a/packages/gamut/package.json
+++ b/packages/gamut/package.json
@@ -16,8 +16,8 @@
"url": "git@github.com:Codecademy/client-modules.git"
},
"peerDependencies": {
- "@emotion/react": "^11.1.4",
- "@emotion/styled": "^11.0.0",
+ "@emotion/react": "^11.1.5",
+ "@emotion/styled": "^11.1.5",
"react": ">=16.8.1",
"react-dom": ">=16.8.1"
},
@@ -34,6 +34,7 @@
"invariant": "^2.2.4",
"lodash": "^4.17.5",
"marked": "^0.7.0",
+ "polished": "^4.1.1",
"react-aria-tabpanel": "^4.4.0",
"react-focus-on": "^3.5.1",
"react-hook-form": "6.8.5",
diff --git a/packages/styleguide/.storybook/components/Docs/DocsContainer.tsx b/packages/styleguide/.storybook/components/Docs/DocsContainer.tsx
index 6428c52638d..f0e12cb48ac 100644
--- a/packages/styleguide/.storybook/components/Docs/DocsContainer.tsx
+++ b/packages/styleguide/.storybook/components/Docs/DocsContainer.tsx
@@ -18,8 +18,6 @@ import React from 'react';
import { merge } from 'lodash';
import { Link } from '../Markdown/Elements';
-const emotionCache = createEmotionCache({ speedy: false });
-
const defaultComponents = {
...htmlComponents,
code: CodeOrSourceMdx,
@@ -34,10 +32,7 @@ export const DocsContainer: React.FC<{ context: DocsContextProps }> = ({
const { parameters = {} } = context || {};
const { docs = {} } = parameters;
- let themeVars = docs.theme;
-
- const theme = ensureTheme(themeVars);
- const overrides = merge({}, theme, {
+ const overrides = merge({}, ensureTheme(docs.theme), {
appBorderRadius: 2,
typography: {
size: {
@@ -49,7 +44,7 @@ export const DocsContainer: React.FC<{ context: DocsContextProps }> = ({
return (
-
+
diff --git a/packages/styleguide/.storybook/components/utils.ts b/packages/styleguide/.storybook/components/utils.ts
index 46e9cb57b4a..30eb22258f6 100644
--- a/packages/styleguide/.storybook/components/utils.ts
+++ b/packages/styleguide/.storybook/components/utils.ts
@@ -1,4 +1,4 @@
-import { colors } from '@codecademy/gamut-styles';
+import { colors } from '@codecademy/gamut-styles/src';
export const selectableColors = Object.keys(colors).reduce<
Record
diff --git a/packages/styleguide/.storybook/decorators/theme.tsx b/packages/styleguide/.storybook/decorators/theme.tsx
index 056590ef1d2..f582ecfae1d 100644
--- a/packages/styleguide/.storybook/decorators/theme.tsx
+++ b/packages/styleguide/.storybook/decorators/theme.tsx
@@ -1,5 +1,4 @@
import React from 'react';
-
import { GamutProvider } from '@codecademy/gamut-styles/src';
/**
@@ -7,7 +6,7 @@ import { GamutProvider } from '@codecademy/gamut-styles/src';
* See: https://github.com/storybookjs/storybook/issues/12255
*/
-export const withEmotion = (Story: any) => {
+export const withEmotion = (Story: any, context: any) => {
// Always give iframes the full provider
if (process.env.NODE_ENV === 'test') {
return (
diff --git a/packages/styleguide/.storybook/preview.ts b/packages/styleguide/.storybook/preview.ts
index 0b74f8266e7..0ac7e81dac0 100644
--- a/packages/styleguide/.storybook/preview.ts
+++ b/packages/styleguide/.storybook/preview.ts
@@ -5,7 +5,7 @@ import { DocsPage, DocsContainer } from './components';
import { theme as gamutTheme } from '@codecademy/gamut-styles/src';
import { theme } from './theme';
-const { colors, breakpoints } = gamutTheme;
+const { breakpoints } = gamutTheme;
export const parameters = {
viewMode: 'docs',
@@ -16,7 +16,7 @@ export const parameters = {
order: [
'Gamut',
'Foundations',
- ['About', 'Theme', 'System', 'Design Guidelines', 'Legacy'],
+ ['About', 'Theme', 'System', 'ColorMode', 'Colors', 'Layout'],
'Typography',
['About', 'Text', 'Anchor'],
'Layouts',
@@ -51,11 +51,6 @@ export const parameters = {
opacity: 0.5,
cellAmount: 5,
},
- values: [
- { name: 'White', value: colors.white },
- { name: 'Navy', value: colors.navy },
- { name: 'Beige', value: colors.beige },
- ],
},
viewport: {
defaultViewport: 'responsive',
@@ -114,6 +109,7 @@ export const parameters = {
},
actions: { argTypesRegex: '^on.*' },
controls: { expanded: true },
+ layout: 'fullscreen',
};
export const decorators = [withEmotion];
diff --git a/packages/styleguide/.storybook/theme.ts b/packages/styleguide/.storybook/theme.ts
index 35722a51921..0a0867bc228 100644
--- a/packages/styleguide/.storybook/theme.ts
+++ b/packages/styleguide/.storybook/theme.ts
@@ -1,5 +1,5 @@
import { create } from '@storybook/theming';
-import { theme as gamutTheme } from '@codecademy/gamut-styles';
+import { theme as gamutTheme, colors } from '@codecademy/gamut-styles/src';
import logo from './assets/logo.svg';
export const theme = create({
@@ -9,28 +9,28 @@ export const theme = create({
brandUrl: '/',
fontBase: gamutTheme.fontFamily.base,
- colorPrimary: gamutTheme.colors.hyper,
- colorSecondary: gamutTheme.colors.navy,
+ colorPrimary: colors.hyper,
+ colorSecondary: colors.navy,
// UI
- appBg: gamutTheme.colors.white,
- appContentBg: gamutTheme.colors.white,
- appBorderColor: gamutTheme.colors.navy,
+ appBg: colors.white,
+ appContentBg: colors.white,
+ appBorderColor: colors.navy,
appBorderRadius: 4,
// Text colors
- textColor: gamutTheme.colors.navy,
- textInverseColor: gamutTheme.colors.white,
- textMutedColor: gamutTheme.colors['gray-800'],
+ textColor: colors.navy,
+ textInverseColor: colors.white,
+ textMutedColor: colors['gray-800'],
// Toolbar default and active colors
- barTextColor: gamutTheme.colors['gray-600'],
- barSelectedColor: gamutTheme.colors.navy,
- barBg: gamutTheme.colors.white,
+ barTextColor: colors['gray-600'],
+ barSelectedColor: colors.navy,
+ barBg: colors.white,
// Form colors
- inputBg: gamutTheme.colors.white,
- inputBorder: gamutTheme.colors.navy,
- inputTextColor: gamutTheme.colors.navy,
+ inputBg: colors.white,
+ inputBorder: colors.navy,
+ inputTextColor: colors.navy,
inputBorderRadius: 2,
});
diff --git a/packages/styleguide/package.json b/packages/styleguide/package.json
index c1c21c2b4f4..aa8fc925692 100644
--- a/packages/styleguide/package.json
+++ b/packages/styleguide/package.json
@@ -42,7 +42,7 @@
"invariant": "2.2.4",
"jsdom": "16.4.0",
"lodash": "4.17.20",
- "polished": "^3.6.5",
+ "polished": "^4.1.1",
"react": "16.13.1",
"react-dom": "16.13.1",
"typescript": "*"
diff --git a/packages/styleguide/stories/Foundations/ColorMode/examples.tsx b/packages/styleguide/stories/Foundations/ColorMode/examples.tsx
new file mode 100644
index 00000000000..6be90aec711
--- /dev/null
+++ b/packages/styleguide/stories/Foundations/ColorMode/examples.tsx
@@ -0,0 +1,64 @@
+/* eslint-disable local-rules/gamut-import-paths */
+import { Box, FlexBox, Text, Toggle } from '@codecademy/gamut/src';
+import { Background, ColorMode } from '@codecademy/gamut-styles/src';
+import React, { ComponentProps, useState } from 'react';
+
+export const ColorModeExample = () => {
+ const [isDark, setIsDark] = useState(false);
+ return (
+
+
+
+ Use Dark Mode
+
+ setIsDark(!isDark)}
+ />
+
+
+
+
+ {isDark ? 'Dark' : 'Light'} Mode
+
+
+ Lorem ipsum dolor sit amet, sed do eiusmod tempor incididunt ut
+ labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud
+ exercitation ullamco laboris nisi ut aliquip ex ea commodo
+ consequat.
+
+
+
+
+ );
+};
+
+export const BackgroundExample: React.FC> = ({
+ children,
+ ...rest
+}) => {
+ return (
+
+
+
+ {rest.initialBackground}
+
+
+ Lorem ipsum dolor sit amet, sed do eiusmod tempor incididunt ut labore
+ et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud
+ exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat.
+
+ {children}
+
+
+ );
+};
diff --git a/packages/styleguide/stories/Foundations/ColorMode/index.stories.mdx b/packages/styleguide/stories/Foundations/ColorMode/index.stories.mdx
new file mode 100644
index 00000000000..5251ad88b6a
--- /dev/null
+++ b/packages/styleguide/stories/Foundations/ColorMode/index.stories.mdx
@@ -0,0 +1,83 @@
+import title from '@codecademy/macros/lib/title.macro';
+import { Meta, Story } from '@storybook/addon-docs/blocks';
+
+import { BackgroundExample, ColorModeExample } from './examples';
+
+
+
+We've created several color modes by default for light and dark context.
+
+Each color mode consists of a set of aliased color tokens. Each alias has semantic meaning for how the color is used throughout our design system:
+
+- `text`: The standard text color for all type.
+- `background`: The base background color.
+- `primary`: The color used for interactive elements with a primary action.
+- `secondary`: The color used for interactive elements with a secondary action.
+
+**Note**: This set is not final and is likely to expand as our needs grow.
+
+Gamut components are built using these aliases instead of referring to specific tokens directly, guaranteeing that:
+
+- Components can be used in ANY context without configuration and work correctly.
+- Consistent color scheme and accessibility between contexts.
+- Colors usage more meaningful when reading through code as aliases hint to usage.
+- Dead simple configuration.
+
+### Light Mode
+
+
+
+### Dark Mode
+
+
+
+## ``
+
+Here's an example using components in context. By toggling dark mode you can see all the colors map to a new color that is accessible for the mode by default.
+
+
+
+### Usage
+
+```tsx
+import { ColorMode } from '@codecademy/gamut-styles';
+
+const Page = ({ children }) => (
+ {children};
+);
+```
+
+## ``
+
+There are many cases where you may need a specific background color for a section of a page, like a card or a landing page. Instead of having to guess the right mode for the background, we've added a `` component to detect if contrast of the background and current mode's text color meet an accessible standard. If not, we will automatically change the mode to an accessible one, allowing you to be sure that all components inside your background will meet contrast requirements and designs without any configuration!
+
+### Usage
+
+```tsx
+import { Background } from '@codecademy/gamut';
+
+const Page = ({ children }) => (
+ {children};
+);
+```
+
+### Examples
+
+Sometimes you may just want to wrap a page in a different background color than the base color mode.
+
+
+
+
+
+In some cases you may want to use multiple areas with different backgrounds. `` makes this a simple task by provisioning a new color context for all components inside.
+
+
+
+
diff --git a/packages/styleguide/stories/Foundations/Theme/index.stories.mdx b/packages/styleguide/stories/Foundations/Theme/index.stories.mdx
index 6a7c227defc..2c24939bb63 100644
--- a/packages/styleguide/stories/Foundations/Theme/index.stories.mdx
+++ b/packages/styleguide/stories/Foundations/Theme/index.stories.mdx
@@ -59,6 +59,33 @@ Swatch colors are accessible with a dashcase key `theme.colors['${color}-${weigh
+## Color Modes
+
+We have 2 core color modes that we support: `light` and `dark`.
+
+Each color mode consists of a set of aliased color tokens. Each alias has semantic meaning for how the color is used throughout our design system:
+
+- `text`: The standard text color for all type.
+- `background`: The base background color.
+- `primary`: The color used for interactive elements with a primary action.
+- `secondary`: The color used for interactive elements with a secondary action.
+
+For more on Color Modes please checkout the [full color mode documentation](/?path=/docs/foundations-colormode--page)
+
+### Light Mode
+
+
+
+
+
+**Example**
+
+### Dark Mode
+
+
+
+
+
## Typography
export const sampleText = 'Example Text';
diff --git a/packages/styleguide/stories/Foundations/Theme/tables.tsx b/packages/styleguide/stories/Foundations/Theme/tables.tsx
index 21c52bff2cf..96179bf22dd 100644
--- a/packages/styleguide/stories/Foundations/Theme/tables.tsx
+++ b/packages/styleguide/stories/Foundations/Theme/tables.tsx
@@ -1,5 +1,11 @@
-import { Box } from '@codecademy/gamut';
-import { swatches, theme, trueColors } from '@codecademy/gamut-styles';
+/* eslint-disable local-rules/gamut-import-paths */
+import { Box } from '@codecademy/gamut/src';
+import {
+ colorModes,
+ swatches,
+ theme,
+ trueColors,
+} from '@codecademy/gamut-styles/src';
import React from 'react';
import { Code, ColorScale } from '~styleguide/blocks';
@@ -24,6 +30,46 @@ const PATH_COLUMN = {
size: 'xl',
};
+export const lightMode = {
+ rows: Object.entries(colorModes.light).map(([id, value]) => ({
+ id,
+ hex: value,
+ })),
+ columns: [
+ PROP_COLUMN,
+ {
+ ...PATH_COLUMN,
+ render: ({ id }: any) => theme.colors.{id},
+ },
+ {
+ key: 'swatch',
+ name: 'Swatch',
+ size: 'fill',
+ render: ({ hex }: any) => ,
+ },
+ ],
+};
+
+export const darkMode = {
+ rows: Object.entries(colorModes.dark).map(([id, value]) => ({
+ id,
+ hex: value,
+ })),
+ columns: [
+ PROP_COLUMN,
+ {
+ ...PATH_COLUMN,
+ render: ({ id }: any) => theme.colors.{id},
+ },
+ {
+ key: 'swatch',
+ name: 'Swatch',
+ size: 'fill',
+ render: ({ hex }: any) => ,
+ },
+ ],
+};
+
export const color = {
rows: Object.entries(trueColors).map(([id, value]) => ({
id,
diff --git a/packages/styleguide/stories/Layouts/Elements/index.tsx b/packages/styleguide/stories/Layouts/Elements/index.tsx
index c42fa2acf27..1e5ddd17179 100644
--- a/packages/styleguide/stories/Layouts/Elements/index.tsx
+++ b/packages/styleguide/stories/Layouts/Elements/index.tsx
@@ -1,5 +1,6 @@
import { Box } from '@codecademy/gamut';
-import { colors } from '@codecademy/gamut-styles';
+// eslint-disable-next-line local-rules/gamut-import-paths
+import { colors } from '@codecademy/gamut-styles/src';
import React from 'react';
export const ExampleBox: React.FC = ({ children }) => (
diff --git a/packages/styleguide/stories/Typography/Anchor.stories.mdx b/packages/styleguide/stories/Typography/Anchor.stories.mdx
index aa00d66a992..41e59ec6735 100644
--- a/packages/styleguide/stories/Typography/Anchor.stories.mdx
+++ b/packages/styleguide/stories/Typography/Anchor.stories.mdx
@@ -1,5 +1,5 @@
import { Anchor, Box } from '@codecademy/gamut/src';
-import { Text } from '@codecademy/gamut-labs';
+import { Text } from '@codecademy/gamut-labs/src';
import title from '@codecademy/macros/lib/title.macro';
import { Canvas, Meta, Story } from '@storybook/addon-docs/blocks';
import { startCase } from 'lodash';
@@ -15,7 +15,6 @@ import { PropsTable } from '~styleguide/blocks';
source: 'gamut',
}}
args={{
- mode: 'light',
href: 'https://www.youtube.com/watch?v=dQw4w9WgXcQ',
target: '_blank',
fontWeight: 'title',
@@ -60,44 +59,14 @@ export const variants = ['interface', 'standard', 'inline'];
## Modes
-### Light
-
-
-
-### Dark
-