diff --git a/packages/gamut-styles/src/index.ts b/packages/gamut-styles/src/index.ts
index 2798e9e46d2..2c9e0399080 100644
--- a/packages/gamut-styles/src/index.ts
+++ b/packages/gamut-styles/src/index.ts
@@ -1,13 +1,14 @@
import '@emotion/react';
-import { Theme as GamutTheme } from './theme';
+import { theme } from './theme';
export * from './cache';
export * from './variables';
export * from './utilities';
-export * from './system';
export * from './theme';
+export type ThemeShape = typeof theme;
+
declare module '@emotion/react' {
- export interface Theme extends GamutTheme {}
+ export interface Theme extends ThemeShape {}
}
diff --git a/packages/gamut-styles/src/theme/GamutThemeProvider.tsx b/packages/gamut-styles/src/theme/GamutThemeProvider.tsx
new file mode 100644
index 00000000000..317f55136c6
--- /dev/null
+++ b/packages/gamut-styles/src/theme/GamutThemeProvider.tsx
@@ -0,0 +1,18 @@
+import { css, Global, ThemeProvider } from '@emotion/react';
+import React from 'react';
+
+import { theme as rawTheme } from './theme';
+import { createThemeVariables } from './utils/createThemeVariables';
+
+export const { theme, cssVariables } = createThemeVariables(rawTheme, [
+ 'elements',
+]);
+
+export const GamutThemeProvider: React.FC = ({ children }) => {
+ return (
+
+
+ {children}
+
+ );
+};
diff --git a/packages/gamut-styles/src/theme/index.ts b/packages/gamut-styles/src/theme/index.ts
new file mode 100644
index 00000000000..b7f36d5e460
--- /dev/null
+++ b/packages/gamut-styles/src/theme/index.ts
@@ -0,0 +1,4 @@
+export * from './GamutThemeProvider';
+export * from './props';
+export { createVariables } from './utils/createVariables';
+export * from './utils/shouldForwardProp';
diff --git a/packages/gamut-styles/src/system.ts b/packages/gamut-styles/src/theme/props.ts
similarity index 77%
rename from packages/gamut-styles/src/system.ts
rename to packages/gamut-styles/src/theme/props.ts
index 45dd64db330..faedd19bc85 100644
--- a/packages/gamut-styles/src/system.ts
+++ b/packages/gamut-styles/src/theme/props.ts
@@ -1,7 +1,5 @@
import { system } from '@codecademy/gamut-system';
-import isPropValid from '@emotion/is-prop-valid';
-
-import { Theme } from './theme';
+import { Theme } from '@emotion/react';
const {
variant,
@@ -71,16 +69,6 @@ const {
},
});
-const allProps = Object.keys(properties).reduce(
- (carry, prop: keyof typeof properties) => {
- return [...carry, ...properties[prop].propNames];
- },
- []
-);
-
-const shouldForwardProp = (prop: string) =>
- isPropValid(prop) && !allProps.includes(prop);
-
export {
variant,
properties,
@@ -94,5 +82,4 @@ export {
shadow,
space,
border,
- shouldForwardProp,
};
diff --git a/packages/gamut-styles/src/theme.tsx b/packages/gamut-styles/src/theme/theme.tsx
similarity index 69%
rename from packages/gamut-styles/src/theme.tsx
rename to packages/gamut-styles/src/theme/theme.tsx
index e474f8de94e..e45bf549d78 100644
--- a/packages/gamut-styles/src/theme.tsx
+++ b/packages/gamut-styles/src/theme/theme.tsx
@@ -1,4 +1,4 @@
-import * as tokens from './variables';
+import * as tokens from '../variables';
export const theme = {
boxShadows: tokens.boxShadows,
@@ -9,8 +9,5 @@ export const theme = {
fontWeight: tokens.fontWeight,
colors: tokens.colors,
spacing: tokens.spacing,
+ elements: tokens.elements,
} as const;
-
-export type ThemeShape = typeof theme;
-
-export interface Theme extends ThemeShape {}
diff --git a/packages/gamut-styles/src/theme/utils/createThemeVariables.ts b/packages/gamut-styles/src/theme/utils/createThemeVariables.ts
new file mode 100644
index 00000000000..4575b472edd
--- /dev/null
+++ b/packages/gamut-styles/src/theme/utils/createThemeVariables.ts
@@ -0,0 +1,75 @@
+import { AbstractTheme } from '@codecademy/gamut-system';
+import { CSSObject } from '@emotion/react';
+import { hasIn, isObject, mapKeys, mapValues, merge } from 'lodash';
+
+import { createVariables } from './createVariables';
+
+/**
+ * Returns an type of any object with { key: 'var(--key) }
+ */
+export type KeyAsVariable> = {
+ [V in keyof T]: `var(--${Extract})`;
+};
+
+/**
+ * Updates the theme type with the correct new values of css variable references
+ */
+export type ThemeWithVariables<
+ Theme extends AbstractTheme,
+ VariableKeys extends (keyof Theme)[]
+> = {
+ [Key in keyof Theme]: Key extends VariableKeys[number]
+ ? KeyAsVariable
+ : Theme[Key];
+};
+
+export interface CreateThemeVars {
+ , VariableKeys extends (keyof Theme)[]>(
+ theme: Theme,
+ keys: VariableKeys
+ ): {
+ cssVariables: CSSObject;
+ theme: ThemeWithVariables;
+ };
+}
+
+const isBreakpoint = (
+ key: string,
+ breakpoints: Required['breakpoints']
+): key is keyof Required['breakpoints'] =>
+ Object.keys(breakpoints).includes(key);
+
+export const createThemeVariables: CreateThemeVars = (theme, keys) => {
+ // Create an empty theme to merge with the base theme object
+ const updatedTheme = { ...theme };
+ // Treat all CSS Variables as a plain emotion CSSObject to be added to.
+ const cssVariables: CSSObject = {};
+
+ keys.forEach((key) => {
+ const tokensToSerialize = { ...theme[key] };
+ // Update the theme object with the new tokens
+ for (const variable in tokensToSerialize) {
+ if (hasIn(tokensToSerialize, variable)) {
+ const variableReference = `var(--${variable})`;
+ updatedTheme[key][variable] = variableReference;
+ }
+ }
+ // Replace breakpoint aliases with the actual selector
+ const replacedBreakpointAliases = mapValues(tokensToSerialize, (val) => {
+ if (isObject(val)) {
+ return mapKeys(val, (val, key) => {
+ return isBreakpoint(key, theme.breakpoints)
+ ? theme.breakpoints[key]
+ : key;
+ });
+ }
+
+ return val;
+ });
+
+ // Create the variables and merge with the rest of the vars
+ merge(cssVariables, createVariables(replacedBreakpointAliases));
+ });
+
+ return { cssVariables, theme: updatedTheme };
+};
diff --git a/packages/gamut-styles/src/theme/utils/createVariables.ts b/packages/gamut-styles/src/theme/utils/createVariables.ts
new file mode 100644
index 00000000000..e4c85e143aa
--- /dev/null
+++ b/packages/gamut-styles/src/theme/utils/createVariables.ts
@@ -0,0 +1,50 @@
+import { CSSObject } from '@emotion/react';
+import { hasIn, merge } from 'lodash';
+
+export const createVariables = (
+ tokens: Record
+) => {
+ const cssVariables: CSSObject = {};
+
+ for (const variable in tokens) {
+ if (!hasIn(tokens, variable)) continue;
+
+ const varName = `--${variable}`;
+ const valuesToRegister = tokens[variable];
+
+ // For all variables in the theme scale add theme to the resulting CSS Object
+ switch (typeof valuesToRegister) {
+ // If the value is primitive just add it as is to the returned vars css object
+ case 'number':
+ case 'string':
+ cssVariables[varName] = valuesToRegister;
+ break;
+ // If the value is an object then attempt to parse it as a resposnive property
+ case 'object':
+ const { base, ...rest } = valuesToRegister;
+
+ // If base key is defined add it to the root values
+ if (base) {
+ cssVariables[varName] = base;
+ }
+
+ // If there are remaining selectors / queries that override the root value add them to style object
+ const selectors = Object.keys(rest);
+ if (selectors) {
+ const overridesBySelector: CSSObject = {};
+ selectors.forEach((selector) => {
+ overridesBySelector[selector] = {
+ [varName]: valuesToRegister[selector],
+ };
+ });
+
+ // Merge with the base object.
+ merge(cssVariables, overridesBySelector);
+ }
+ break;
+ default:
+ break;
+ }
+ }
+ return cssVariables;
+};
diff --git a/packages/gamut-styles/src/theme/utils/shouldForwardProp.ts b/packages/gamut-styles/src/theme/utils/shouldForwardProp.ts
new file mode 100644
index 00000000000..819ff5fc4e5
--- /dev/null
+++ b/packages/gamut-styles/src/theme/utils/shouldForwardProp.ts
@@ -0,0 +1,13 @@
+import isPropValid from '@emotion/is-prop-valid';
+
+import { properties } from '../props';
+
+const allProps = Object.keys(properties).reduce(
+ (carry, prop: keyof typeof properties) => {
+ return [...carry, ...properties[prop].propNames];
+ },
+ []
+);
+
+export const shouldForwardProp = (prop: string) =>
+ isPropValid(prop) && !allProps.includes(prop);
diff --git a/packages/gamut-styles/src/variables/elements.ts b/packages/gamut-styles/src/variables/elements.ts
new file mode 100644
index 00000000000..03f3cda38f9
--- /dev/null
+++ b/packages/gamut-styles/src/variables/elements.ts
@@ -0,0 +1,3 @@
+export const elements = {
+ headerHeight: { base: '4rem', md: '5rem' },
+} as const;
diff --git a/packages/gamut-styles/src/variables/index.ts b/packages/gamut-styles/src/variables/index.ts
index 9a0a0a121af..38b0689a25d 100644
--- a/packages/gamut-styles/src/variables/index.ts
+++ b/packages/gamut-styles/src/variables/index.ts
@@ -5,5 +5,6 @@ export * from './typography';
export * from './effects';
export * from './shadows';
export * from './timing';
+export * from './elements';
// Deprecated variables
export * from './deprecated-colors';
diff --git a/packages/gamut-styles/src/variables/responsive.ts b/packages/gamut-styles/src/variables/responsive.ts
index a6820dae1c3..1237a50e529 100644
--- a/packages/gamut-styles/src/variables/responsive.ts
+++ b/packages/gamut-styles/src/variables/responsive.ts
@@ -8,9 +8,8 @@ export const breakpoints: Record = {
xl: '1440px',
};
-const createMediaQuery = (size: MediaSize, direction: 'min' | 'max') => `
- @media only screen and (${direction}-width: ${breakpoints[size]})
-`;
+const createMediaQuery = (size: MediaSize, direction: 'min' | 'max') =>
+ `@media only screen and (${direction}-width: ${breakpoints[size]})`;
export const mediaQueries = {
xs: createMediaQuery('xs', 'min'),
diff --git a/packages/gamut-tests/src/index.tsx b/packages/gamut-tests/src/index.tsx
index b9231786eac..93a3f754515 100644
--- a/packages/gamut-tests/src/index.tsx
+++ b/packages/gamut-tests/src/index.tsx
@@ -1,5 +1,4 @@
-import { theme } from '@codecademy/gamut-styles';
-import { ThemeProvider } from '@emotion/react';
+import { GamutThemeProvider } from '@codecademy/gamut-styles';
import {
setupEnzyme as setupEnzymeBase,
setupRtl as setupRtlBase,
@@ -13,9 +12,9 @@ function withThemeProvider(
WrappedComponent: React.ComponentType
) {
const WithBoundaryComponent: React.FC = (props) => (
-
+
-
+
);
return WithBoundaryComponent;
diff --git a/packages/styleguide/.storybook/addons/system/enhancers.ts b/packages/styleguide/.storybook/addons/system/enhancers.ts
index 377b63fa5d6..4baf51c80d5 100644
--- a/packages/styleguide/.storybook/addons/system/enhancers.ts
+++ b/packages/styleguide/.storybook/addons/system/enhancers.ts
@@ -2,7 +2,8 @@ import { mapValues, isNumber, map } from 'lodash/fp';
import { ArgTypesEnhancer } from '@storybook/client-api';
import { kebabCase } from 'lodash';
import { ALL_PROPS, PROP_META, PROP_GROUPS } from './propMeta';
-import { Theme, theme } from '@codecademy/gamut-styles/src/theme';
+import { theme } from '@codecademy/gamut-styles/src/theme';
+import { Theme } from '@emotion/react';
export type SystemControls = 'text' | 'select' | 'radio' | 'inline-radio';
diff --git a/packages/styleguide/.storybook/addons/system/propMeta.ts b/packages/styleguide/.storybook/addons/system/propMeta.ts
index f8be47b6013..7cf7bd3fa51 100644
--- a/packages/styleguide/.storybook/addons/system/propMeta.ts
+++ b/packages/styleguide/.storybook/addons/system/propMeta.ts
@@ -1,6 +1,6 @@
-import * as system from '@codecademy/gamut-styles/src/system';
+import * as system from '@codecademy/gamut-styles/src/theme/props';
-const { shouldForwardProp, properties, variant, ...groups } = system;
+const { properties, variant, ...groups } = system;
export const PROP_GROUPS = groups;
diff --git a/packages/styleguide/.storybook/components/PropsTable/constants.ts b/packages/styleguide/.storybook/components/PropsTable/constants.ts
index 86a11901db2..511b468a1a1 100644
--- a/packages/styleguide/.storybook/components/PropsTable/constants.ts
+++ b/packages/styleguide/.storybook/components/PropsTable/constants.ts
@@ -1,4 +1,4 @@
-import * as system from '@codecademy/gamut-styles/src/system';
+import * as system from '@codecademy/gamut-styles/src/theme/props';
const { shouldForwardProp, properties: props, variant, ...groups } = system;
diff --git a/packages/styleguide/.storybook/decorators/theme.tsx b/packages/styleguide/.storybook/decorators/theme.tsx
index 1b6f5c52e6b..45f47961157 100644
--- a/packages/styleguide/.storybook/decorators/theme.tsx
+++ b/packages/styleguide/.storybook/decorators/theme.tsx
@@ -1,7 +1,10 @@
import React from 'react';
-import { CacheProvider, ThemeContext } from '@emotion/react';
+import { CacheProvider } from '@emotion/react';
-import { theme, createEmotionCache } from '@codecademy/gamut-styles';
+import {
+ GamutThemeProvider,
+ createEmotionCache,
+} from '@codecademy/gamut-styles';
const cache = createEmotionCache();
@@ -12,10 +15,10 @@ const cache = createEmotionCache();
export const withEmotion = (Story: any) => {
return process.env.NODE_ENV === 'test' ? (
- {Story()}
+ {Story()}
) : (
- {Story()}
+ {Story()}
);
};