From 0a298ce8e8c40ed7b2c45da53d40dd582961d30b Mon Sep 17 00:00:00 2001 From: georgewrmarshall Date: Mon, 6 Jun 2022 16:20:09 -0700 Subject: [PATCH] Adding themes to CSS-in-JS architecture --- README.md | 11 +- docs/IntroductionColor.stories.mdx | 4 +- docs/components/ColorSwatch/ColorSwatch.tsx | 14 +- .../ColorSwatchGroup/ColorSwatchGroup.tsx | 6 +- src/index.ts | 2 +- src/js/colors/colors.test.ts | 2 - src/js/colors/colors.ts | 197 +-------------- src/js/index.ts | 5 + src/js/themes/darkTheme/colors.test.ts | 228 ++++++++++++++++++ src/js/themes/darkTheme/colors.ts | 131 ++++++++++ src/js/themes/darkTheme/darkTheme.test.ts | 18 ++ src/js/themes/darkTheme/darkTheme.ts | 8 + src/js/themes/darkTheme/index.ts | 1 + src/js/themes/index.ts | 3 + src/js/themes/lightTheme/colors.test.ts | 228 ++++++++++++++++++ src/js/themes/lightTheme/colors.ts | 131 ++++++++++ src/js/themes/lightTheme/index.ts | 1 + src/js/themes/lightTheme/lightTheme.test.ts | 18 ++ src/js/themes/lightTheme/lightTheme.ts | 8 + src/js/{colors => themes}/types.ts | 10 +- src/js/typography/index.ts | 1 + 21 files changed, 813 insertions(+), 214 deletions(-) create mode 100644 src/js/themes/darkTheme/colors.test.ts create mode 100644 src/js/themes/darkTheme/colors.ts create mode 100644 src/js/themes/darkTheme/darkTheme.test.ts create mode 100644 src/js/themes/darkTheme/darkTheme.ts create mode 100644 src/js/themes/darkTheme/index.ts create mode 100644 src/js/themes/index.ts create mode 100644 src/js/themes/lightTheme/colors.test.ts create mode 100644 src/js/themes/lightTheme/colors.ts create mode 100644 src/js/themes/lightTheme/index.ts create mode 100644 src/js/themes/lightTheme/lightTheme.test.ts create mode 100644 src/js/themes/lightTheme/lightTheme.ts rename src/js/{colors => themes}/types.ts (87%) diff --git a/README.md b/README.md index 432f100b..d2be3ec9 100644 --- a/README.md +++ b/README.md @@ -79,13 +79,16 @@ yarn add @metamask/design-tokens 2. Use design tokens in code by importing from library: ```js -import { colors } from '@metamask/design-tokens'; +import { lightTheme, darkTheme } from '@metamask/design-tokens'; -const createStyles = (colors) => +// Create provider that swaps theme (sudo code) +; + +const createStyles = (theme) => StyleSheet.create({ modalContainer: { - backgroundColor: colors.background.default, - borderColor: colors.border.default, + backgroundColor: theme.colors.background.default, + borderColor: theme.colors.border.default, }, }); ``` diff --git a/docs/IntroductionColor.stories.mdx b/docs/IntroductionColor.stories.mdx index 890a95ac..0ebba576 100644 --- a/docs/IntroductionColor.stories.mdx +++ b/docs/IntroductionColor.stories.mdx @@ -1,7 +1,7 @@ import { Meta } from '@storybook/addon-docs'; import designTokenDiagramImage from './images/design.token.graphic.svg'; -import tokens from '../src/figma/tokens.json'; +import { lightTheme } from '../src/index.ts'; @@ -16,7 +16,7 @@ We follow a 3 tiered system for color design tokens.
diff --git a/docs/components/ColorSwatch/ColorSwatch.tsx b/docs/components/ColorSwatch/ColorSwatch.tsx index 6459cc1d..ac1f52fb 100644 --- a/docs/components/ColorSwatch/ColorSwatch.tsx +++ b/docs/components/ColorSwatch/ColorSwatch.tsx @@ -1,5 +1,5 @@ import React, { FunctionComponent } from 'react'; -import { colors } from '../../../src'; +import { lightTheme } from '../../../src'; interface ColorSwatchProps { /** @@ -7,15 +7,15 @@ interface ColorSwatchProps { */ color?: string; /** - * The color of text background that contains the name of the color defaults to colors.light.background.default + * The color of text background that contains the name of the color defaults to lightTheme.colors.background.default */ textBackgroundColor?: string; /** - * The border color of the swatch defaults to colors.light.border.muted + * The border color of the swatch defaults to lightTheme.colors.border.muted */ borderColor?: string; /** - * The color of the text defaults to colors.light.text.default + * The color of the text defaults to lightTheme.colors.text.default */ textColor?: string; /** @@ -26,9 +26,9 @@ interface ColorSwatchProps { export const ColorSwatch: FunctionComponent = ({ color, - borderColor = colors.light.border.muted, - textBackgroundColor = colors.light.background.default, - textColor = colors.light.text.default, + borderColor = lightTheme.colors.border.muted, + textBackgroundColor = lightTheme.colors.background.default, + textColor = lightTheme.colors.text.default, name, ...props }) => { diff --git a/docs/components/ColorSwatchGroup/ColorSwatchGroup.tsx b/docs/components/ColorSwatchGroup/ColorSwatchGroup.tsx index 5355abc8..471918eb 100644 --- a/docs/components/ColorSwatchGroup/ColorSwatchGroup.tsx +++ b/docs/components/ColorSwatchGroup/ColorSwatchGroup.tsx @@ -27,15 +27,15 @@ interface ColorSwatchGroupProps { [key: string]: ColorToken; }; /** - * The color of text background that contains the name of the color defaults to colors.light.background.default + * The color of text background that contains the name of the color defaults to background.default */ textBackgroundColor?: string; /** - * The border color of the swatch defaults to colors.light.border.muted + * The border color of the swatch defaults to border.muted */ borderColor?: string; /** - * The color of the text defaults to colors.light.text.default + * The color of the text defaults to text.default */ textColor?: string; /** diff --git a/src/index.ts b/src/index.ts index 14ff07a3..3c79023c 100644 --- a/src/index.ts +++ b/src/index.ts @@ -1 +1 @@ -export { colors, typography } from './js'; +export { colors, typography, lightTheme, darkTheme, Theme } from './js'; diff --git a/src/js/colors/colors.test.ts b/src/js/colors/colors.test.ts index c345585a..467951c6 100644 --- a/src/js/colors/colors.test.ts +++ b/src/js/colors/colors.test.ts @@ -3,8 +3,6 @@ import { colors as importableColors } from '.'; const designTokens = require('../../figma/tokens.json'); -// js tokens for font family matches figma tokens - describe('Light Theme Colors', () => { it('js tokens for background.default matches figma tokens background.default', () => { expect(importableColors.light.background.default).toStrictEqual( diff --git a/src/js/colors/colors.ts b/src/js/colors/colors.ts index 977ebb69..4ce9be11 100644 --- a/src/js/colors/colors.ts +++ b/src/js/colors/colors.ts @@ -1,199 +1,14 @@ -import { Colors } from './types'; +import { lightTheme, darkTheme } from '../themes'; -/* eslint-disable jsdoc/check-property-names, jsdoc/tag-lines */ /** - * MetaMask's Design System Colors - * - * NOTE - This documentation doesn't reflect the actual structure of the colors object. We are only documenting the common properties between light and dark themes. - * - * @property {string} background.default - For default neutral backgrounds - * @property {string} background.alternative - For a subtle contrast option for neutral backgrounds. (Example: backdrop, header background) - * - * @property {string} text.default - For general text that takes main priority in the information hierarchy - * @property {string} text.alternative - For a weaker contrast option for neutral text - * @property {string} text.muted - For inactive or lowest priority text. (Example: placeholder) - * - * @property {string} icon.default - For default neutral icons - * @property {string} icon.alternative - For a weaker contrast option for neutral icons - * @property {string} icon.muted - For inactive or lowest priority icons - * - * @property {string} border.default - For default neutral borders with visible contrast. (Example: text inputs) - * @property {string} border.muted - For a weaker contrast option for neutral borders. - * - * @property {string} overlay.default - For shading layers behind modality screens - * @property {string} overlay.alternative - For a stronger shading layer option behind modality screens - * @property {string} overlay.inverse - [Deprecated] Should be used for elements over an overlay - * - * @property {string} primary.default - For primary user action related elements - * @property {string} primary.alternative - For the \"pressed\" state of interactive primary elements - * @property {string} primary.muted - For lowest contrast background used in primary elements - * @property {string} primary.disabled - [Deprecated] Should be used for disabled state - * @property {string} primary.inverse - For elements used on top of primary/default. (Example: label of primary button, check in a checkbox) - * - * @property {string} secondary.default - [Deprecated] Should be used for any secondary actions. It should not be used for any negative connotations such as warnings or errors as it is quite closely tied to the MetaMask Fox - * @property {string} secondary.alternative - [Deprecated] Should be used as an alternative to secondary.default for things such as hover states - * @property {string} secondary.muted - [Deprecated] It’s a very low contrasting secondary variant for things such as alert backgrounds. secondary.muted and secondary.inverse should not be used together in a foreground and background combination - * @property {string} secondary.disabled - [Deprecated] Should be used for all disabled secondary action components - * @property {string} secondary.inverse - [Deprecated] Should be used only as the foreground element on top of primary/default and primary/alternative. It is intended to be the most contrasting color to primary/default. It should meet all AA and AAA accessibility standards such as the text or icon of a primary button - * - * @property {string} error.default - For high-level alert danger/critical elements. Used for text, background, icon or border - * @property {string} error.alternative - For the \"pressed\" state of interactive danger/critical elements - * @property {string} error.muted - For lowest contrast background used in high-level alert danger/critical elements. (Example: notification background) - * @property {string} error.disabled - [Deprecated] Should be used for disabled state - * @property {string} error.inverse - For elements used on top of error/default (Example: label of danger/critical button) - * - * @property {string} warning.default - For low-mid level alert elements. Used for text, background, icon or border - * @property {string} warning.alternative - [Deprecated] Should be used as an alternative to warning/default for things like hover or pressed states - * @property {string} warning.muted - For lowest contrast background used in warning elements. (Example: notification background) - * @property {string} warning.disabled - [Deprecated] Should be used for disabled state - * @property {string} warning.inverse - For elements used on top of warning/default. Used for text, icon or border - * - * @property {string} success.default - For positive & good semantic elements. Used for text, background, icon or border - * @property {string} success.alternative - [Deprecated] Should be used as an alternative to success/default for things like hover or pressed states - * @property {string} success.muted - For lowest contrast background used in success semantic. (Example: notification background) - * @property {string} success.disabled - [Deprecated] Should be used for disabled state - * @property {string} success.inverse - For elements used on top of success/default. Used for text, icon or border - * - * @property {string} info.default - For informational semantic elements. Used for text, background, icon or border - * @property {string} info.alternative - [Deprecated] Should be used as an alternative to info/default for things like hover or pressed states - * @property {string} info.muted - For lowest contrast background used in informational semantic. (Example: notification background) - * @property {string} info.disabled - [Deprecated] Should be used for disabled state - * @property {string} info.inverse - SFor elements used on top of info/default. Used for text, icon or border + * This object is DEPRECATED in favour of the theme objects */ -/* eslint-enable jsdoc/check-property-names, jsdoc/tag-lines */ -export const colors: Colors = { + +export const colors = { light: { - background: { - default: '#FFFFFF', - alternative: '#F2F4F6', - }, - text: { - default: '#24272A', - alternative: '#535A61', - muted: '#BBC0C5', - }, - icon: { - default: '#24272A', - alternative: '#6A737D', - muted: '#BBC0C5', - }, - border: { - default: '#BBC0C5', - muted: '#D6D9DC', - }, - overlay: { - default: '#00000099', - inverse: '#FCFCFC', - alternative: '#000000CC', - }, - primary: { - default: '#037DD6', - alternative: '#0260A4', - muted: '#037DD619', - inverse: '#FCFCFC', - disabled: '#037DD680', - }, - secondary: { - default: '#F66A0A', - alternative: '#C65507', - muted: '#F66A0A19', - inverse: '#FCFCFC', - disabled: '#F66A0A80', - }, - error: { - default: '#D73A49', - alternative: '#B92534', - muted: '#D73A4919', - inverse: '#FCFCFC', - disabled: '#D73A4980', - }, - warning: { - default: '#F66A0A', - alternative: '#FFC70A', - muted: '#FFD33D19', - inverse: '#FCFCFC', - disabled: '#FFD33D80', - }, - success: { - default: '#28A745', - alternative: '#1E7E34', - muted: '#28A74519', - inverse: '#FCFCFC', - disabled: '#28A74580', - }, - info: { - default: '#037DD6', - alternative: '#0260A4', - muted: '#037DD619', - inverse: '#FCFCFC', - disabled: '#037DD680', - }, + ...lightTheme.colors, }, dark: { - background: { - default: '#24272A', - alternative: '#141618', - }, - text: { - default: '#FFFFFF', - alternative: '#D6D9DC', - muted: '#9FA6AE', - }, - icon: { - default: '#FFFFFF', - alternative: '#BBC0C5', - muted: '#9FA6AE', - }, - border: { - default: '#848C96', - muted: '#3B4046', - }, - overlay: { - default: '#00000099', - inverse: '#FCFCFC', - alternative: '#000000CC', - }, - primary: { - default: '#1098FC', - alternative: '#43AEFC', - muted: '#1098FC26', - inverse: '#FCFCFC', - disabled: '#1098FC80', - }, - secondary: { - default: '#F8883B', - alternative: '#FAA66C', - muted: '#F8883B26', - inverse: '#FCFCFC', - disabled: '#F8883B80', - }, - error: { - default: '#D73A49', - alternative: '#E06470', - muted: '#D73A4926', - inverse: '#FCFCFC', - disabled: '#D73A4980', - }, - warning: { - default: '#FFD33D', - alternative: '#FFDF70', - muted: '#FFD33D26', - inverse: '#141618', - disabled: '#FFD33D80', - }, - success: { - default: '#28A745', - alternative: '#5DD879', - muted: '#28A74526', - inverse: '#FCFCFC', - disabled: '#28A74580', - }, - info: { - default: '#1098FC', - alternative: '#43AEFC', - muted: '#1098FC26', - inverse: '#FCFCFC', - disabled: '#037DD680', - }, + ...darkTheme.colors, }, }; diff --git a/src/js/index.ts b/src/js/index.ts index 3c9e4f2f..5fa681c3 100644 --- a/src/js/index.ts +++ b/src/js/index.ts @@ -1,2 +1,7 @@ +export { Theme } from './themes'; +export { lightTheme } from './themes'; +export { darkTheme } from './themes'; + +// DEPRECATED in favor of importing theme objects above export { colors } from './colors'; export { typography } from './typography'; diff --git a/src/js/themes/darkTheme/colors.test.ts b/src/js/themes/darkTheme/colors.test.ts new file mode 100644 index 00000000..c4802be5 --- /dev/null +++ b/src/js/themes/darkTheme/colors.test.ts @@ -0,0 +1,228 @@ +/* eslint-disable @typescript-eslint/no-var-requires, @typescript-eslint/no-require-imports */ +import { colors as importableColors } from './colors'; + +const designTokens = require('../../../figma/tokens.json'); + +describe('Dark Theme Colors', () => { + it('js tokens for background.default matches figma tokens background.default', () => { + expect(importableColors.background.default).toStrictEqual( + designTokens.dark.colors.background.default.value, + ); + }); + + it('js tokens for background.alternative matches figma tokens background.alternative', () => { + expect(importableColors.background.alternative).toStrictEqual( + designTokens.dark.colors.background.alternative.value, + ); + }); + + it('js tokens for text.default matches figma tokens text.default', () => { + expect(importableColors.text.default).toStrictEqual( + designTokens.dark.colors.text.default.value, + ); + }); + + it('js tokens for text.alternative matches figma tokens text.alternative', () => { + expect(importableColors.text.alternative).toStrictEqual( + designTokens.dark.colors.text.alternative.value, + ); + }); + + it('js tokens for text.muted matches figma tokens text.muted', () => { + expect(importableColors.text.muted).toStrictEqual( + designTokens.dark.colors.text.muted.value, + ); + }); + + it('js tokens for icon.default matches figma tokens icon.default', () => { + expect(importableColors.icon.default).toStrictEqual( + designTokens.dark.colors.icon.default.value, + ); + }); + + it('js tokens for icon.alternative matches figma tokens icon.alternative', () => { + expect(importableColors.icon.alternative).toStrictEqual( + designTokens.dark.colors.icon.alternative.value, + ); + }); + + it('js tokens for icon.muted matches figma tokens icon.muted', () => { + expect(importableColors.icon.muted).toStrictEqual( + designTokens.dark.colors.icon.muted.value, + ); + }); + + it('js tokens for border.default matches figma tokens border.default', () => { + expect(importableColors.border.default).toStrictEqual( + designTokens.dark.colors.border.default.value, + ); + }); + + it('js tokens for border.muted matches figma tokens border.muted', () => { + expect(importableColors.border.muted).toStrictEqual( + designTokens.dark.colors.border.muted.value, + ); + }); + + it('js tokens for overlay.default matches figma tokens overlay.default', () => { + expect(importableColors.overlay.default).toStrictEqual( + designTokens.dark.colors.overlay.default.value, + ); + }); + + it('js tokens for overlay.alternative matches figma tokens overlay.alternative', () => { + expect(importableColors.overlay.alternative).toStrictEqual( + designTokens.dark.colors.overlay.alternative.value, + ); + }); + + it('js tokens for overlay.inverse matches figma tokens overlay.inverse', () => { + expect(importableColors.overlay.inverse).toStrictEqual( + designTokens.dark.colors.overlay.inverse.value, + ); + }); + + it('js tokens for primary.default matches figma tokens primary.default', () => { + expect(importableColors.primary.default).toStrictEqual( + designTokens.dark.colors.primary.default.value, + ); + }); + + it('js tokens for primary.alternative matches figma tokens primary.alternative', () => { + expect(importableColors.primary.alternative).toStrictEqual( + designTokens.dark.colors.primary.alternative.value, + ); + }); + + it('js tokens for primary.muted matches figma tokens primary.muted', () => { + expect(importableColors.primary.muted).toStrictEqual( + designTokens.dark.colors.primary.muted.value, + ); + }); + + it('js tokens for primary.inverse matches figma tokens primary.inverse', () => { + expect(importableColors.primary.inverse).toStrictEqual( + designTokens.dark.colors.primary.inverse.value, + ); + }); + + it('js tokens for secondary.default matches figma tokens secondary.default', () => { + expect(importableColors.secondary.default).toStrictEqual( + designTokens.dark.colors.secondary.default.value, + ); + }); + + it('js tokens for secondary.alternative matches figma tokens secondary.alternative', () => { + expect(importableColors.secondary.alternative).toStrictEqual( + designTokens.dark.colors.secondary.alternative.value, + ); + }); + + it('js tokens for secondary.muted matches figma tokens secondary.muted', () => { + expect(importableColors.secondary.muted).toStrictEqual( + designTokens.dark.colors.secondary.muted.value, + ); + }); + + it('js tokens for secondary.inverse matches figma tokens secondary.inverse', () => { + expect(importableColors.secondary.inverse).toStrictEqual( + designTokens.dark.colors.secondary.inverse.value, + ); + }); + + it('js tokens for error.default matches figma tokens error.default', () => { + expect(importableColors.error.default).toStrictEqual( + designTokens.dark.colors.error.default.value, + ); + }); + + it('js tokens for error.alternative matches figma tokens error.alternative', () => { + expect(importableColors.error.alternative).toStrictEqual( + designTokens.dark.colors.error.alternative.value, + ); + }); + + it('js tokens for error.muted matches figma tokens error.muted', () => { + expect(importableColors.error.muted).toStrictEqual( + designTokens.dark.colors.error.muted.value, + ); + }); + + it('js tokens for error.inverse matches figma tokens error.inverse', () => { + expect(importableColors.error.inverse).toStrictEqual( + designTokens.dark.colors.error.inverse.value, + ); + }); + + it('js tokens for warning.default matches figma tokens warning.default', () => { + expect(importableColors.warning.default).toStrictEqual( + designTokens.dark.colors.warning.default.value, + ); + }); + + it('js tokens for warning.alternative matches figma tokens warning.alternative', () => { + expect(importableColors.warning.alternative).toStrictEqual( + designTokens.dark.colors.warning.alternative.value, + ); + }); + + it('js tokens for warning.muted matches figma tokens warning.muted', () => { + expect(importableColors.warning.muted).toStrictEqual( + designTokens.dark.colors.warning.muted.value, + ); + }); + + it('js tokens for warning.inverse matches figma tokens warning.inverse', () => { + expect(importableColors.warning.inverse).toStrictEqual( + designTokens.dark.colors.warning.inverse.value, + ); + }); + + it('js tokens for success.default matches figma tokens success.default', () => { + expect(importableColors.success.default).toStrictEqual( + designTokens.dark.colors.success.default.value, + ); + }); + + it('js tokens for success.alternative matches figma tokens success.alternative', () => { + expect(importableColors.success.alternative).toStrictEqual( + designTokens.dark.colors.success.alternative.value, + ); + }); + + it('js tokens for success.muted matches figma tokens success.muted', () => { + expect(importableColors.success.muted).toStrictEqual( + designTokens.dark.colors.success.muted.value, + ); + }); + + it('js tokens for success.inverse matches figma tokens success.inverse', () => { + expect(importableColors.success.inverse).toStrictEqual( + designTokens.dark.colors.success.inverse.value, + ); + }); + + it('js tokens for info.default matches figma tokens info.default', () => { + expect(importableColors.info.default).toStrictEqual( + designTokens.dark.colors.info.default.value, + ); + }); + + it('js tokens for info.alternative matches figma tokens info.alternative', () => { + expect(importableColors.info.alternative).toStrictEqual( + designTokens.dark.colors.info.alternative.value, + ); + }); + + it('js tokens for info.muted matches figma tokens info.muted', () => { + expect(importableColors.info.muted).toStrictEqual( + designTokens.dark.colors.info.muted.value, + ); + }); + + it('js tokens for info.inverse matches figma tokens info.inverse', () => { + expect(importableColors.info.inverse).toStrictEqual( + designTokens.dark.colors.info.inverse.value, + ); + }); +}); diff --git a/src/js/themes/darkTheme/colors.ts b/src/js/themes/darkTheme/colors.ts new file mode 100644 index 00000000..47618656 --- /dev/null +++ b/src/js/themes/darkTheme/colors.ts @@ -0,0 +1,131 @@ +import { ThemeColors } from '../types'; + +/* eslint-disable jsdoc/check-property-names, jsdoc/tag-lines */ +/** + * MetaMask's Design System Colors + * + * NOTE - This documentation doesn't reflect the actual structure of the colors object. We are only documenting the common properties between light and dark themes. + * + * @property {string} background.default - For default neutral backgrounds + * @property {string} background.alternative - For a subtle contrast option for neutral backgrounds. (Example: backdrop, header background) + * + * @property {string} text.default - For general text that takes main priority in the information hierarchy + * @property {string} text.alternative - For a weaker contrast option for neutral text + * @property {string} text.muted - For inactive or lowest priority text. (Example: placeholder) + * + * @property {string} icon.default - For default neutral icons + * @property {string} icon.alternative - For a weaker contrast option for neutral icons + * @property {string} icon.muted - For inactive or lowest priority icons + * + * @property {string} border.default - For default neutral borders with visible contrast. (Example: text inputs) + * @property {string} border.muted - For a weaker contrast option for neutral borders. + * + * @property {string} overlay.default - For shading layers behind modality screens + * @property {string} overlay.alternative - For a stronger shading layer option behind modality screens + * @property {string} overlay.inverse - [Deprecated] Should be used for elements over an overlay + * + * @property {string} primary.default - For primary user action related elements + * @property {string} primary.alternative - For the \"pressed\" state of interactive primary elements + * @property {string} primary.muted - For lowest contrast background used in primary elements + * @property {string} primary.disabled - [Deprecated] Should be used for disabled state + * @property {string} primary.inverse - For elements used on top of primary/default. (Example: label of primary button, check in a checkbox) + * + * @property {string} secondary.default - [Deprecated] Should be used for any secondary actions. It should not be used for any negative connotations such as warnings or errors as it is quite closely tied to the MetaMask Fox + * @property {string} secondary.alternative - [Deprecated] Should be used as an alternative to secondary.default for things such as hover states + * @property {string} secondary.muted - [Deprecated] It’s a very low contrasting secondary variant for things such as alert backgrounds. secondary.muted and secondary.inverse should not be used together in a foreground and background combination + * @property {string} secondary.disabled - [Deprecated] Should be used for all disabled secondary action components + * @property {string} secondary.inverse - [Deprecated] Should be used only as the foreground element on top of primary/default and primary/alternative. It is intended to be the most contrasting color to primary/default. It should meet all AA and AAA accessibility standards such as the text or icon of a primary button + * + * @property {string} error.default - For high-level alert danger/critical elements. Used for text, background, icon or border + * @property {string} error.alternative - For the \"pressed\" state of interactive danger/critical elements + * @property {string} error.muted - For lowest contrast background used in high-level alert danger/critical elements. (Example: notification background) + * @property {string} error.disabled - [Deprecated] Should be used for disabled state + * @property {string} error.inverse - For elements used on top of error/default (Example: label of danger/critical button) + * + * @property {string} warning.default - For low-mid level alert elements. Used for text, background, icon or border + * @property {string} warning.alternative - [Deprecated] Should be used as an alternative to warning/default for things like hover or pressed states + * @property {string} warning.muted - For lowest contrast background used in warning elements. (Example: notification background) + * @property {string} warning.disabled - [Deprecated] Should be used for disabled state + * @property {string} warning.inverse - For elements used on top of warning/default. Used for text, icon or border + * + * @property {string} success.default - For positive & good semantic elements. Used for text, background, icon or border + * @property {string} success.alternative - [Deprecated] Should be used as an alternative to success/default for things like hover or pressed states + * @property {string} success.muted - For lowest contrast background used in success semantic. (Example: notification background) + * @property {string} success.disabled - [Deprecated] Should be used for disabled state + * @property {string} success.inverse - For elements used on top of success/default. Used for text, icon or border + * + * @property {string} info.default - For informational semantic elements. Used for text, background, icon or border + * @property {string} info.alternative - [Deprecated] Should be used as an alternative to info/default for things like hover or pressed states + * @property {string} info.muted - For lowest contrast background used in informational semantic. (Example: notification background) + * @property {string} info.disabled - [Deprecated] Should be used for disabled state + * @property {string} info.inverse - SFor elements used on top of info/default. Used for text, icon or border + */ +/* eslint-enable jsdoc/check-property-names, jsdoc/tag-lines */ + +export const colors: ThemeColors = { + background: { + default: '#24272A', + alternative: '#141618', + }, + text: { + default: '#FFFFFF', + alternative: '#D6D9DC', + muted: '#9FA6AE', + }, + icon: { + default: '#FFFFFF', + alternative: '#BBC0C5', + muted: '#9FA6AE', + }, + border: { + default: '#848C96', + muted: '#3B4046', + }, + overlay: { + default: '#00000099', + inverse: '#FCFCFC', + alternative: '#000000CC', + }, + primary: { + default: '#1098FC', + alternative: '#43AEFC', + muted: '#1098FC26', + inverse: '#FCFCFC', + disabled: '#1098FC80', + }, + secondary: { + default: '#F8883B', + alternative: '#FAA66C', + muted: '#F8883B26', + inverse: '#FCFCFC', + disabled: '#F8883B80', + }, + error: { + default: '#D73A49', + alternative: '#E06470', + muted: '#D73A4926', + inverse: '#FCFCFC', + disabled: '#D73A4980', + }, + warning: { + default: '#FFD33D', + alternative: '#FFDF70', + muted: '#FFD33D26', + inverse: '#141618', + disabled: '#FFD33D80', + }, + success: { + default: '#28A745', + alternative: '#5DD879', + muted: '#28A74526', + inverse: '#FCFCFC', + disabled: '#28A74580', + }, + info: { + default: '#1098FC', + alternative: '#43AEFC', + muted: '#1098FC26', + inverse: '#FCFCFC', + disabled: '#037DD680', + }, +}; diff --git a/src/js/themes/darkTheme/darkTheme.test.ts b/src/js/themes/darkTheme/darkTheme.test.ts new file mode 100644 index 00000000..36ef1a57 --- /dev/null +++ b/src/js/themes/darkTheme/darkTheme.test.ts @@ -0,0 +1,18 @@ +/* eslint-disable @typescript-eslint/no-var-requires, @typescript-eslint/no-require-imports */ +import { darkTheme } from './darkTheme'; + +const designTokens = require('../../../figma/tokens.json'); + +describe('dark Theme', () => { + it('color tokens are exported from darkTheme by checking first color token', () => { + expect(darkTheme.colors.background.default).toStrictEqual( + designTokens.dark.colors.background.default.value, + ); + }); + + it('typography tokens are exported from darkTheme by checking first typography token', () => { + expect(darkTheme.typography.sDisplayMD.fontFamily).toStrictEqual( + designTokens.global.fontFamilies['euclid-circular-b'].value, + ); + }); +}); diff --git a/src/js/themes/darkTheme/darkTheme.ts b/src/js/themes/darkTheme/darkTheme.ts new file mode 100644 index 00000000..cd2f29a1 --- /dev/null +++ b/src/js/themes/darkTheme/darkTheme.ts @@ -0,0 +1,8 @@ +import { Theme } from '../types'; +import { typography } from '../../typography'; +import { colors } from './colors'; + +export const darkTheme: Theme = { + colors, + typography, +}; diff --git a/src/js/themes/darkTheme/index.ts b/src/js/themes/darkTheme/index.ts new file mode 100644 index 00000000..577487a2 --- /dev/null +++ b/src/js/themes/darkTheme/index.ts @@ -0,0 +1 @@ +export { darkTheme } from './darkTheme'; diff --git a/src/js/themes/index.ts b/src/js/themes/index.ts new file mode 100644 index 00000000..6ee787ab --- /dev/null +++ b/src/js/themes/index.ts @@ -0,0 +1,3 @@ +export { lightTheme } from './lightTheme'; +export { darkTheme } from './darkTheme'; +export { Theme } from './types'; diff --git a/src/js/themes/lightTheme/colors.test.ts b/src/js/themes/lightTheme/colors.test.ts new file mode 100644 index 00000000..65bad480 --- /dev/null +++ b/src/js/themes/lightTheme/colors.test.ts @@ -0,0 +1,228 @@ +/* eslint-disable @typescript-eslint/no-var-requires, @typescript-eslint/no-require-imports */ +import { colors as importableColors } from './colors'; + +const designTokens = require('../../../figma/tokens.json'); + +describe('Light Theme Colors', () => { + it('js tokens for background.default matches figma tokens background.default', () => { + expect(importableColors.background.default).toStrictEqual( + designTokens.light.colors.background.default.value, + ); + }); + + it('js tokens for background.alternative matches figma tokens background.alternative', () => { + expect(importableColors.background.alternative).toStrictEqual( + designTokens.light.colors.background.alternative.value, + ); + }); + + it('js tokens for text.default matches figma tokens text.default', () => { + expect(importableColors.text.default).toStrictEqual( + designTokens.light.colors.text.default.value, + ); + }); + + it('js tokens for text.alternative matches figma tokens text.alternative', () => { + expect(importableColors.text.alternative).toStrictEqual( + designTokens.light.colors.text.alternative.value, + ); + }); + + it('js tokens for text.muted matches figma tokens text.muted', () => { + expect(importableColors.text.muted).toStrictEqual( + designTokens.light.colors.text.muted.value, + ); + }); + + it('js tokens for icon.default matches figma tokens icon.default', () => { + expect(importableColors.icon.default).toStrictEqual( + designTokens.light.colors.icon.default.value, + ); + }); + + it('js tokens for icon.alternative matches figma tokens icon.alternative', () => { + expect(importableColors.icon.alternative).toStrictEqual( + designTokens.light.colors.icon.alternative.value, + ); + }); + + it('js tokens for icon.muted matches figma tokens icon.muted', () => { + expect(importableColors.icon.muted).toStrictEqual( + designTokens.light.colors.icon.muted.value, + ); + }); + + it('js tokens for border.default matches figma tokens border.default', () => { + expect(importableColors.border.default).toStrictEqual( + designTokens.light.colors.border.default.value, + ); + }); + + it('js tokens for border.muted matches figma tokens border.muted', () => { + expect(importableColors.border.muted).toStrictEqual( + designTokens.light.colors.border.muted.value, + ); + }); + + it('js tokens for overlay.default matches figma tokens overlay.default', () => { + expect(importableColors.overlay.default).toStrictEqual( + designTokens.light.colors.overlay.default.value, + ); + }); + + it('js tokens for overlay.alternative matches figma tokens overlay.alternative', () => { + expect(importableColors.overlay.alternative).toStrictEqual( + designTokens.light.colors.overlay.alternative.value, + ); + }); + + it('js tokens for overlay.inverse matches figma tokens overlay.inverse', () => { + expect(importableColors.overlay.inverse).toStrictEqual( + designTokens.light.colors.overlay.inverse.value, + ); + }); + + it('js tokens for primary.default matches figma tokens primary.default', () => { + expect(importableColors.primary.default).toStrictEqual( + designTokens.light.colors.primary.default.value, + ); + }); + + it('js tokens for primary.alternative matches figma tokens primary.alternative', () => { + expect(importableColors.primary.alternative).toStrictEqual( + designTokens.light.colors.primary.alternative.value, + ); + }); + + it('js tokens for primary.muted matches figma tokens primary.muted', () => { + expect(importableColors.primary.muted).toStrictEqual( + designTokens.light.colors.primary.muted.value, + ); + }); + + it('js tokens for primary.inverse matches figma tokens primary.inverse', () => { + expect(importableColors.primary.inverse).toStrictEqual( + designTokens.light.colors.primary.inverse.value, + ); + }); + + it('js tokens for secondary.default matches figma tokens secondary.default', () => { + expect(importableColors.secondary.default).toStrictEqual( + designTokens.light.colors.secondary.default.value, + ); + }); + + it('js tokens for secondary.alternative matches figma tokens secondary.alternative', () => { + expect(importableColors.secondary.alternative).toStrictEqual( + designTokens.light.colors.secondary.alternative.value, + ); + }); + + it('js tokens for secondary.muted matches figma tokens secondary.muted', () => { + expect(importableColors.secondary.muted).toStrictEqual( + designTokens.light.colors.secondary.muted.value, + ); + }); + + it('js tokens for secondary.inverse matches figma tokens secondary.inverse', () => { + expect(importableColors.secondary.inverse).toStrictEqual( + designTokens.light.colors.secondary.inverse.value, + ); + }); + + it('js tokens for error.default matches figma tokens error.default', () => { + expect(importableColors.error.default).toStrictEqual( + designTokens.light.colors.error.default.value, + ); + }); + + it('js tokens for error.alternative matches figma tokens error.alternative', () => { + expect(importableColors.error.alternative).toStrictEqual( + designTokens.light.colors.error.alternative.value, + ); + }); + + it('js tokens for error.muted matches figma tokens error.muted', () => { + expect(importableColors.error.muted).toStrictEqual( + designTokens.light.colors.error.muted.value, + ); + }); + + it('js tokens for error.inverse matches figma tokens error.inverse', () => { + expect(importableColors.error.inverse).toStrictEqual( + designTokens.light.colors.error.inverse.value, + ); + }); + + it('js tokens for warning.default matches figma tokens warning.default', () => { + expect(importableColors.warning.default).toStrictEqual( + designTokens.light.colors.warning.default.value, + ); + }); + + it('js tokens for warning.alternative matches figma tokens warning.alternative', () => { + expect(importableColors.warning.alternative).toStrictEqual( + designTokens.light.colors.warning.alternative.value, + ); + }); + + it('js tokens for warning.muted matches figma tokens warning.muted', () => { + expect(importableColors.warning.muted).toStrictEqual( + designTokens.light.colors.warning.muted.value, + ); + }); + + it('js tokens for warning.inverse matches figma tokens warning.inverse', () => { + expect(importableColors.warning.inverse).toStrictEqual( + designTokens.light.colors.warning.inverse.value, + ); + }); + + it('js tokens for success.default matches figma tokens success.default', () => { + expect(importableColors.success.default).toStrictEqual( + designTokens.light.colors.success.default.value, + ); + }); + + it('js tokens for success.alternative matches figma tokens success.alternative', () => { + expect(importableColors.success.alternative).toStrictEqual( + designTokens.light.colors.success.alternative.value, + ); + }); + + it('js tokens for success.muted matches figma tokens success.muted', () => { + expect(importableColors.success.muted).toStrictEqual( + designTokens.light.colors.success.muted.value, + ); + }); + + it('js tokens for success.inverse matches figma tokens success.inverse', () => { + expect(importableColors.success.inverse).toStrictEqual( + designTokens.light.colors.success.inverse.value, + ); + }); + + it('js tokens for info.default matches figma tokens info.default', () => { + expect(importableColors.info.default).toStrictEqual( + designTokens.light.colors.info.default.value, + ); + }); + + it('js tokens for info.alternative matches figma tokens info.alternative', () => { + expect(importableColors.info.alternative).toStrictEqual( + designTokens.light.colors.info.alternative.value, + ); + }); + + it('js tokens for info.muted matches figma tokens info.muted', () => { + expect(importableColors.info.muted).toStrictEqual( + designTokens.light.colors.info.muted.value, + ); + }); + + it('js tokens for info.inverse matches figma tokens info.inverse', () => { + expect(importableColors.info.inverse).toStrictEqual( + designTokens.light.colors.info.inverse.value, + ); + }); +}); diff --git a/src/js/themes/lightTheme/colors.ts b/src/js/themes/lightTheme/colors.ts new file mode 100644 index 00000000..85004326 --- /dev/null +++ b/src/js/themes/lightTheme/colors.ts @@ -0,0 +1,131 @@ +import { ThemeColors } from '../types'; + +/* eslint-disable jsdoc/check-property-names, jsdoc/tag-lines */ +/** + * MetaMask's Design System Colors + * + * NOTE - This documentation doesn't reflect the actual structure of the colors object. We are only documenting the common properties between light and dark themes. + * + * @property {string} background.default - For default neutral backgrounds + * @property {string} background.alternative - For a subtle contrast option for neutral backgrounds. (Example: backdrop, header background) + * + * @property {string} text.default - For general text that takes main priority in the information hierarchy + * @property {string} text.alternative - For a weaker contrast option for neutral text + * @property {string} text.muted - For inactive or lowest priority text. (Example: placeholder) + * + * @property {string} icon.default - For default neutral icons + * @property {string} icon.alternative - For a weaker contrast option for neutral icons + * @property {string} icon.muted - For inactive or lowest priority icons + * + * @property {string} border.default - For default neutral borders with visible contrast. (Example: text inputs) + * @property {string} border.muted - For a weaker contrast option for neutral borders. + * + * @property {string} overlay.default - For shading layers behind modality screens + * @property {string} overlay.alternative - For a stronger shading layer option behind modality screens + * @property {string} overlay.inverse - [Deprecated] Should be used for elements over an overlay + * + * @property {string} primary.default - For primary user action related elements + * @property {string} primary.alternative - For the \"pressed\" state of interactive primary elements + * @property {string} primary.muted - For lowest contrast background used in primary elements + * @property {string} primary.disabled - [Deprecated] Should be used for disabled state + * @property {string} primary.inverse - For elements used on top of primary/default. (Example: label of primary button, check in a checkbox) + * + * @property {string} secondary.default - [Deprecated] Should be used for any secondary actions. It should not be used for any negative connotations such as warnings or errors as it is quite closely tied to the MetaMask Fox + * @property {string} secondary.alternative - [Deprecated] Should be used as an alternative to secondary.default for things such as hover states + * @property {string} secondary.muted - [Deprecated] It’s a very low contrasting secondary variant for things such as alert backgrounds. secondary.muted and secondary.inverse should not be used together in a foreground and background combination + * @property {string} secondary.disabled - [Deprecated] Should be used for all disabled secondary action components + * @property {string} secondary.inverse - [Deprecated] Should be used only as the foreground element on top of primary/default and primary/alternative. It is intended to be the most contrasting color to primary/default. It should meet all AA and AAA accessibility standards such as the text or icon of a primary button + * + * @property {string} error.default - For high-level alert danger/critical elements. Used for text, background, icon or border + * @property {string} error.alternative - For the \"pressed\" state of interactive danger/critical elements + * @property {string} error.muted - For lowest contrast background used in high-level alert danger/critical elements. (Example: notification background) + * @property {string} error.disabled - [Deprecated] Should be used for disabled state + * @property {string} error.inverse - For elements used on top of error/default (Example: label of danger/critical button) + * + * @property {string} warning.default - For low-mid level alert elements. Used for text, background, icon or border + * @property {string} warning.alternative - [Deprecated] Should be used as an alternative to warning/default for things like hover or pressed states + * @property {string} warning.muted - For lowest contrast background used in warning elements. (Example: notification background) + * @property {string} warning.disabled - [Deprecated] Should be used for disabled state + * @property {string} warning.inverse - For elements used on top of warning/default. Used for text, icon or border + * + * @property {string} success.default - For positive & good semantic elements. Used for text, background, icon or border + * @property {string} success.alternative - [Deprecated] Should be used as an alternative to success/default for things like hover or pressed states + * @property {string} success.muted - For lowest contrast background used in success semantic. (Example: notification background) + * @property {string} success.disabled - [Deprecated] Should be used for disabled state + * @property {string} success.inverse - For elements used on top of success/default. Used for text, icon or border + * + * @property {string} info.default - For informational semantic elements. Used for text, background, icon or border + * @property {string} info.alternative - [Deprecated] Should be used as an alternative to info/default for things like hover or pressed states + * @property {string} info.muted - For lowest contrast background used in informational semantic. (Example: notification background) + * @property {string} info.disabled - [Deprecated] Should be used for disabled state + * @property {string} info.inverse - SFor elements used on top of info/default. Used for text, icon or border + */ +/* eslint-enable jsdoc/check-property-names, jsdoc/tag-lines */ + +export const colors: ThemeColors = { + background: { + default: '#FFFFFF', + alternative: '#F2F4F6', + }, + text: { + default: '#24272A', + alternative: '#535A61', + muted: '#BBC0C5', + }, + icon: { + default: '#24272A', + alternative: '#6A737D', + muted: '#BBC0C5', + }, + border: { + default: '#BBC0C5', + muted: '#D6D9DC', + }, + overlay: { + default: '#00000099', + inverse: '#FCFCFC', + alternative: '#000000CC', + }, + primary: { + default: '#037DD6', + alternative: '#0260A4', + muted: '#037DD619', + inverse: '#FCFCFC', + disabled: '#037DD680', + }, + secondary: { + default: '#F66A0A', + alternative: '#C65507', + muted: '#F66A0A19', + inverse: '#FCFCFC', + disabled: '#F66A0A80', + }, + error: { + default: '#D73A49', + alternative: '#B92534', + muted: '#D73A4919', + inverse: '#FCFCFC', + disabled: '#D73A4980', + }, + warning: { + default: '#F66A0A', + alternative: '#FFC70A', + muted: '#FFD33D19', + inverse: '#FCFCFC', + disabled: '#FFD33D80', + }, + success: { + default: '#28A745', + alternative: '#1E7E34', + muted: '#28A74519', + inverse: '#FCFCFC', + disabled: '#28A74580', + }, + info: { + default: '#037DD6', + alternative: '#0260A4', + muted: '#037DD619', + inverse: '#FCFCFC', + disabled: '#037DD680', + }, +}; diff --git a/src/js/themes/lightTheme/index.ts b/src/js/themes/lightTheme/index.ts new file mode 100644 index 00000000..4783cb11 --- /dev/null +++ b/src/js/themes/lightTheme/index.ts @@ -0,0 +1 @@ +export { lightTheme } from './lightTheme'; diff --git a/src/js/themes/lightTheme/lightTheme.test.ts b/src/js/themes/lightTheme/lightTheme.test.ts new file mode 100644 index 00000000..3bb72b98 --- /dev/null +++ b/src/js/themes/lightTheme/lightTheme.test.ts @@ -0,0 +1,18 @@ +/* eslint-disable @typescript-eslint/no-var-requires, @typescript-eslint/no-require-imports */ +import { lightTheme } from './lightTheme'; + +const designTokens = require('../../../figma/tokens.json'); + +describe('Light Theme', () => { + it('color tokens are exported from lightTheme by checking first color token', () => { + expect(lightTheme.colors.background.default).toStrictEqual( + designTokens.light.colors.background.default.value, + ); + }); + + it('typography tokens are exported from lightTheme by checking first typography token', () => { + expect(lightTheme.typography.sDisplayMD.fontFamily).toStrictEqual( + designTokens.global.fontFamilies['euclid-circular-b'].value, + ); + }); +}); diff --git a/src/js/themes/lightTheme/lightTheme.ts b/src/js/themes/lightTheme/lightTheme.ts new file mode 100644 index 00000000..b5fc19c1 --- /dev/null +++ b/src/js/themes/lightTheme/lightTheme.ts @@ -0,0 +1,8 @@ +import { Theme } from '../types'; +import { typography } from '../../typography'; +import { colors } from './colors'; + +export const lightTheme: Theme = { + colors, + typography, +}; diff --git a/src/js/colors/types.ts b/src/js/themes/types.ts similarity index 87% rename from src/js/colors/types.ts rename to src/js/themes/types.ts index 9937662b..99ad57f9 100644 --- a/src/js/colors/types.ts +++ b/src/js/themes/types.ts @@ -1,4 +1,6 @@ -interface ThemeColors { +import { ThemeTypography } from '../typography'; + +export interface ThemeColors { background: { default: string; alternative: string; @@ -66,7 +68,7 @@ interface ThemeColors { }; } -export interface Colors { - light: ThemeColors; - dark: ThemeColors; +export interface Theme { + colors: ThemeColors; + typography: ThemeTypography; } diff --git a/src/js/typography/index.ts b/src/js/typography/index.ts index 3781f7c4..8e841838 100644 --- a/src/js/typography/index.ts +++ b/src/js/typography/index.ts @@ -1 +1,2 @@ export { typography } from './typography'; +export { ThemeTypography } from './types';