Skip to content

Commit

Permalink
Feature/4073 ps text component (MetaMask#4265)
Browse files Browse the repository at this point in the history
* Begin creating BaseText component

* Create useStyles hook to abstract applying theme and vars to stylesheet

* Update design-tokens library to introduce typography

* Update design tokens library to fix typography font weight

* Create BaseText component. Provide types and styles.

* Remove old files

* Update BaseText with README, story, and test.

* Refine BaseText test

* Add BaseText to stories

* Clean up BaseText. Mock useAppThemeFromContext

* Update BaseText storybook

* Temporarily fallback to mock theme to patch storybook theme context

* Fix tests

* Update comments

* Update base style

* Fix docs

* Add default color to text component

* Fix unit test
  • Loading branch information
Cal-L authored May 12, 2022
1 parent fb7184c commit a53213f
Show file tree
Hide file tree
Showing 21 changed files with 231 additions and 18 deletions.
12 changes: 12 additions & 0 deletions app/component-library/components/BaseText/BaseText.stories.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
import React from 'react';
import { storiesOf } from '@storybook/react-native';
import BaseText, { BaseTextVariant } from './';

storiesOf('Component Library / BaseText', module)
.addDecorator((getStory) => getStory())
.add('Small Display MD', () => (
<BaseText variant={BaseTextVariant.sDisplayMD}>{`I'm Text!`}</BaseText>
))
.add('Small Body MD', () => (
<BaseText variant={BaseTextVariant.sBodyMD}>{`I'm Text!`}</BaseText>
));
28 changes: 28 additions & 0 deletions app/component-library/components/BaseText/BaseText.styles.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
import { StyleSheet, TextStyle } from 'react-native';
import { BaseTextStyleSheet, BaseTextStyleSheetVars } from './BaseText.types';
import { Theme } from '../../../util/theme/models';

/**
* Style sheet function for BaseText component.
*
* @param params Style sheet params.
* @param params.theme App theme from ThemeContext.
* @param params.vars Inputs that the style sheet depends on.
* @returns StyleSheet object.
*/
const styleSheet = (params: {
theme: Theme;
vars: BaseTextStyleSheetVars;
}): BaseTextStyleSheet => {
const { theme, vars } = params;
const { variant, style } = vars;
return StyleSheet.create({
base: Object.assign(
{ color: theme.colors.text.default },
theme.typography[variant],
style,
) as TextStyle,
});
};

export default styleSheet;
12 changes: 12 additions & 0 deletions app/component-library/components/BaseText/BaseText.test.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
import React from 'react';
import { shallow } from 'enzyme';
import BaseText, { BaseTextVariant } from './';

describe('BaseText', () => {
it('should render correctly', () => {
const wrapper = shallow(
<BaseText variant={BaseTextVariant.lBodyMD}>{`I'm Text!`}</BaseText>,
);
expect(wrapper).toMatchSnapshot();
});
});
22 changes: 22 additions & 0 deletions app/component-library/components/BaseText/BaseText.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
/* eslint-disable react/prop-types */
import React from 'react';
import { Text } from 'react-native';
import { useStyles } from '../../hooks';
import styleSheet from './BaseText.styles';
import { BaseTextProps } from './BaseText.types';

const BaseText: React.FC<BaseTextProps> = ({
variant,
style,
children,
...props
}) => {
const styles = useStyles(styleSheet, { variant, style });
return (
<Text {...props} style={styles.base}>
{children}
</Text>
);
};

export default BaseText;
57 changes: 57 additions & 0 deletions app/component-library/components/BaseText/BaseText.types.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,57 @@
import { StyleProp, TextProps, TextStyle } from 'react-native';

/**
* BaseText component variants.
*/
export enum BaseTextVariant {
sDisplayMD = 'sDisplayMD',
sHeadingLG = 'sHeadingLG',
sHeadingMD = 'sHeadingMD',
sHeadingSMRegular = 'sHeadingSMRegular',
sHeadingSM = 'sHeadingSM',
sBodyMD = 'sBodyMD',
sBodyMDBold = 'sBodyMDBold',
sBodySM = 'sBodySM',
sBodySMBold = 'sBodySMBold',
sBodyXS = 'sBodyXS',
lDisplayMD = 'lDisplayMD',
lHeadingLG = 'lHeadingLG',
lHeadingMD = 'lHeadingMD',
lHeadingSMRegular = 'lHeadingSMRegular',
lHeadingSM = 'lHeadingSM',
lBodyMD = 'lBodyMD',
lBodyMDBold = 'lBodyMDBold',
lBodySM = 'lBodySM',
lBodySMBold = 'lBodySMBold',
lBodyXS = 'lBodyXS',
}

/**
* BaseText component props.
*/
export interface BaseTextProps extends TextProps {
/**
* Enum to select between Typography variants.
*/
variant: BaseTextVariant;
/**
* Escape hatch for applying extra styles. Only use if absolutely necessary.
*/
style?: StyleProp<TextStyle>;
/**
* Children component of a Text component.
*/
children?: React.ReactNode;
}

/**
* BaseText component style sheet.
*/
export interface BaseTextStyleSheet {
base: TextStyle;
}

/**
* Style sheet input parameters.
*/
export type BaseTextStyleSheetVars = Pick<BaseTextProps, 'variant' | 'style'>;
15 changes: 15 additions & 0 deletions app/component-library/components/BaseText/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
# BaseText

BaseText is a text component that standardizes on typography provided through theme via @metamask/design-tokens library.

## Props

This component extends `TextProps` from React Native's [Text Component](https://reactnative.dev/docs/text).

### `variant`

Enum to select between Typography variants.

| <span style="color:gray;font-size:14px">TYPE</span> | <span style="color:gray;font-size:14px">REQUIRED</span> |
| :-------------------------------------------------- | :------------------------------------------------------ |
| [BaseTextVariant](./BaseText.types.ts#L6) | Yes |
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
// Jest Snapshot v1, https://goo.gl/fbAQLP

exports[`BaseText should render correctly 1`] = `
<Text
style={
Object {
"color": "#24272A",
"fontFamily": "Euclid Circular B",
"fontSize": 16,
"fontWeight": "400",
"letterSpacing": 0,
"lineHeight": 24,
}
}
>
I'm Text!
</Text>
`;
2 changes: 2 additions & 0 deletions app/component-library/components/BaseText/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
export { default } from './BaseText';
export { BaseTextVariant } from './BaseText.types';
2 changes: 2 additions & 0 deletions app/component-library/hooks/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
/* eslint-disable import/prefer-default-export */
export { useStyles } from './useStyles';
23 changes: 23 additions & 0 deletions app/component-library/hooks/useStyles.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
/* eslint-disable import/prefer-default-export */
import { useMemo } from 'react';
import { useAppThemeFromContext } from '../../util/theme';
import { Theme } from '../../util/theme/models';

/**
* Hook that handles both passing style sheet variables into style sheet and memoization.
*
* @param styleSheet Return value of useStyles hook.
* @param vars Variables of styleSheet function.
* @returns StyleSheet object.
*/
export const useStyles = <R, V>(
styleSheet: (params: { theme: Theme; vars: V }) => R,
vars: V,
): R => {
const theme = useAppThemeFromContext();
const styles = useMemo(
() => styleSheet({ theme, vars }),
[styleSheet, theme, vars],
);
return styles;
};
Original file line number Diff line number Diff line change
Expand Up @@ -39,7 +39,7 @@ exports[`BrowserBottomBar should render correctly 1`] = `
style={
Array [
Object {
"color": "#6A737D",
"color": "#24272A",
"height": 24,
"textAlign": "center",
"width": 24,
Expand Down Expand Up @@ -73,7 +73,7 @@ exports[`BrowserBottomBar should render correctly 1`] = `
style={
Array [
Object {
"color": "#6A737D",
"color": "#24272A",
"height": 24,
"textAlign": "center",
"width": 24,
Expand Down Expand Up @@ -107,7 +107,7 @@ exports[`BrowserBottomBar should render correctly 1`] = `
size={24}
style={
Object {
"color": "#6A737D",
"color": "#24272A",
"height": 24,
"textAlign": "center",
"width": 24,
Expand Down Expand Up @@ -163,7 +163,7 @@ exports[`BrowserBottomBar should render correctly 1`] = `
size={22}
style={
Object {
"color": "#6A737D",
"color": "#24272A",
"height": 24,
"textAlign": "center",
"width": 24,
Expand Down Expand Up @@ -193,7 +193,7 @@ exports[`BrowserBottomBar should render correctly 1`] = `
size={22}
style={
Object {
"color": "#6A737D",
"color": "#24272A",
"height": 24,
"textAlign": "center",
"width": 24,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@ exports[`Collectibles should render correctly 1`] = `
]
}
refreshing={false}
tintColor="#6A737D"
tintColor="#24272A"
/>
}
>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -253,7 +253,7 @@ exports[`CustomNonceModal should render correctly 1`] = `
style={
Object {
"backgroundColor": "#FFD33D19",
"borderColor": "#FFD33D",
"borderColor": "#F66A0A",
"borderRadius": 8,
"borderWidth": 1,
"display": "flex",
Expand All @@ -266,7 +266,7 @@ exports[`CustomNonceModal should render correctly 1`] = `
>
<Icon
allowFontScaling={false}
color="#FFD33D"
color="#F66A0A"
name="exclamation-circle"
size={16}
style={
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -43,7 +43,7 @@ exports[`SelectComponent should render correctly 1`] = `
/>
<Icon
allowFontScaling={false}
color="#6A737D"
color="#24272A"
name="arrow-drop-down"
size={24}
style={
Expand Down
6 changes: 6 additions & 0 deletions app/util/testSetup.js
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,8 @@ import NotificationManager from '../core/NotificationManager';
import { NativeModules } from 'react-native';
import mockAsyncStorage from '../../node_modules/@react-native-community/async-storage/jest/async-storage-mock';
import mockClipboard from '@react-native-clipboard/clipboard/jest/clipboard-mock.js';
/* eslint-disable import/no-namespace */
import * as themeUtils from './theme';

Enzyme.configure({ adapter: new Adapter() });

Expand Down Expand Up @@ -156,3 +158,7 @@ jest.mock('react-native/Libraries/Interaction/InteractionManager', () => ({

jest.mock('../images/static-logos.js', () => ({}));
jest.mock('@react-native-clipboard/clipboard', () => mockClipboard);
jest.mock('../util/theme', () => ({
...themeUtils,
useAppThemeFromContext: () => themeUtils.mockTheme,
}));
10 changes: 7 additions & 3 deletions app/util/theme/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,14 +2,18 @@ import React, { useContext } from 'react';
import { useColorScheme, StatusBar, ColorSchemeName } from 'react-native';
import { Colors, AppThemeKey, Theme } from './models';
import { useSelector } from 'react-redux';
import { colors as colorTheme } from '@metamask/design-tokens';
import { colors as colorTheme, typography } from '@metamask/design-tokens';
import Device from '../device';

/**
* This is needed to make our unit tests pass since Enzyme doesn't support contextType
* TODO: Convert classes into functional components and remove contextType
*/
export const mockTheme = { colors: colorTheme.light, themeAppearance: 'light' };
export const mockTheme = {
colors: colorTheme.light,
themeAppearance: 'light',
typography,
};

export const ThemeContext = React.createContext<any>(undefined);

Expand Down Expand Up @@ -102,7 +106,7 @@ export const useAppTheme = (): Theme => {
setLightStatusBar();
}

return { colors, themeAppearance };
return { colors, themeAppearance, typography };
};

export const useAppThemeFromContext = (): Theme => {
Expand Down
3 changes: 3 additions & 0 deletions app/util/theme/models.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
import { ThemeTypography } from '@metamask/design-tokens/dist/js/typography/types';

// TODO: This should probably be defined from @metamask/design-token library
export type Colors = any;

Expand All @@ -8,5 +10,6 @@ export enum AppThemeKey {
}
export interface Theme {
colors: Colors;
typography: ThemeTypography;
themeAppearance: AppThemeKey.light | AppThemeKey.dark;
}
5 changes: 3 additions & 2 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -103,7 +103,7 @@
"@keystonehq/ur-decoder": "^0.3.0",
"@metamask/contract-metadata": "^1.30.0",
"@metamask/controllers": "28.0.0",
"@metamask/design-tokens": "^1.5.1",
"@metamask/design-tokens": "^1.6.5",
"@metamask/etherscan-link": "^2.0.0",
"@metamask/swaps-controller": "^6.6.0",
"@ngraveio/bc-ur": "^1.1.6",
Expand Down Expand Up @@ -315,7 +315,8 @@
"config": {
"react-native-storybook-loader": {
"searchDir": [
"./app/components"
"./app/components",
"./app/component-library"
],
"pattern": "**/*.stories.@(js|tsx)",
"outputFile": "./storybook/storyLoader.js"
Expand Down
6 changes: 6 additions & 0 deletions storybook/index.js
Original file line number Diff line number Diff line change
@@ -1,16 +1,22 @@
import React from 'react';
import {
getStorybookUI,
configure,
addDecorator,
} from '@storybook/react-native';
import { withKnobs } from '@storybook/addon-knobs';
import { ThemeContext, mockTheme } from '../app/util/theme';

import { loadStories } from './storyLoader';

import './rn-addons';

// enables knobs for all stories
addDecorator(withKnobs);
// enables theme for all stories - TODO - make theme dynamic instead of mocked
addDecorator((storyFn) => (
<ThemeContext.Provider value={mockTheme}>{storyFn()}</ThemeContext.Provider>
));

// import stories locally and from the
// react-native-storybook-loader auto generated file
Expand Down
2 changes: 2 additions & 0 deletions storybook/storyLoader.js
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ function loadStories() {
require('../app/components/UI/ButtonReveal/ButtonReveal.stories');
require('../app/components/UI/Fox/Fox.stories');
require('../app/components/UI/StyledButton/StyledButton.stories');
require('../app/component-library/components/BaseText/BaseText.stories');
}

const stories = [
Expand All @@ -21,6 +22,7 @@ const stories = [
'../app/components/UI/ButtonReveal/ButtonReveal.stories',
'../app/components/UI/Fox/Fox.stories',
'../app/components/UI/StyledButton/StyledButton.stories',
'../app/component-library/components/BaseText/BaseText.stories',
];

module.exports = {
Expand Down
Loading

0 comments on commit a53213f

Please sign in to comment.