Skip to content

Commit

Permalink
feat(component): add text modifiers to typography (#240)
Browse files Browse the repository at this point in the history
  • Loading branch information
deini authored Nov 8, 2019
1 parent a933b45 commit f64c4b4
Show file tree
Hide file tree
Showing 8 changed files with 254 additions and 33 deletions.
4 changes: 2 additions & 2 deletions packages/big-design/src/components/Form/Error/Error.tsx
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import React from 'react';

import { Small, SmallProps } from '../../Typography';
import { Small, TextProps } from '../../Typography';

export const Error: React.FC<SmallProps> = ({ className, style, ...props }) => (
export const Error: React.FC<TextProps> = ({ className, style, ...props }) => (
<Small color="danger" margin="none" marginLeft="xxSmall" {...props} />
);
45 changes: 27 additions & 18 deletions packages/big-design/src/components/Typography/Typography.tsx
Original file line number Diff line number Diff line change
@@ -1,19 +1,9 @@
import { Colors, ThemeInterface } from '@bigcommerce/big-design-theme';
import React, { memo } from 'react';

import { MarginProps } from '../../mixins';

import { StyledH0, StyledH1, StyledH2, StyledH3, StyledH4, StyledSmall, StyledText } from './styled';
import { HeadingProps, HeadingTag, TextProps } from './types';

export interface TypographyProps {
color?: keyof Colors;
ellipsis?: boolean;
theme?: ThemeInterface;
}

export type TextProps = React.HTMLAttributes<HTMLParagraphElement> & MarginProps & TypographyProps;
export type SmallProps = React.HTMLAttributes<HTMLParagraphElement> & MarginProps & TypographyProps;
export type HeadingProps = React.HTMLAttributes<HTMLHeadingElement> & MarginProps & TypographyProps;
const validHeadingTags = new Set<HeadingTag>(['h1', 'h2', 'h3', 'h4', 'h5', 'h6']);

// Private
export const StyleableText = StyledText;
Expand All @@ -26,9 +16,28 @@ export const StyleableH4 = StyledH4;

// Public
export const Text: React.FC<TextProps> = memo(({ className, style, ...props }) => <StyleableText {...props} />);
export const Small: React.FC<SmallProps> = memo(({ className, style, ...props }) => <StyleableSmall {...props} />);
export const H0: React.FC<HeadingProps> = memo(({ className, style, ...props }) => <StyleableH0 {...props} />);
export const H1: React.FC<HeadingProps> = memo(({ className, style, ...props }) => <StyleableH1 {...props} />);
export const H2: React.FC<HeadingProps> = memo(({ className, style, ...props }) => <StyleableH2 {...props} />);
export const H3: React.FC<HeadingProps> = memo(({ className, style, ...props }) => <StyleableH3 {...props} />);
export const H4: React.FC<HeadingProps> = memo(({ className, style, ...props }) => <StyleableH4 {...props} />);
export const Small: React.FC<TextProps> = memo(({ className, style, ...props }) => <StyleableSmall {...props} />);

export const H0: React.FC<HeadingProps> = memo(({ className, style, as, ...props }) => (
<StyleableH0 as={getHeadingTag('h1', as)} {...props} />
));

export const H1: React.FC<HeadingProps> = memo(({ className, style, as, ...props }) => (
<StyleableH1 as={getHeadingTag('h1', as)} {...props} />
));

export const H2: React.FC<HeadingProps> = memo(({ className, style, as, ...props }) => (
<StyleableH2 as={getHeadingTag('h2', as)} {...props} />
));

export const H3: React.FC<HeadingProps> = memo(({ className, style, as, ...props }) => (
<StyleableH3 as={getHeadingTag('h3', as)} {...props} />
));

export const H4: React.FC<HeadingProps> = memo(({ className, style, as, ...props }) => (
<StyleableH4 as={getHeadingTag('h4', as)} {...props} />
));

const getHeadingTag = (defaultTag: HeadingTag, tag?: HeadingTag): HeadingTag => {
return tag && validHeadingTags.has(tag) ? tag : defaultTag;
};
3 changes: 1 addition & 2 deletions packages/big-design/src/components/Typography/index.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,6 @@
import { HeadingProps as _HeadingProps, SmallProps as _SmallProps, TextProps as _TextProps } from './Typography';
import { HeadingProps as _HeadingProps, TextProps as _TextProps } from './types';

export { Text, Small, H0, H1, H2, H3, H4 } from './Typography';

export type TextProps = _TextProps;
export type SmallProps = _SmallProps;
export type HeadingProps = _HeadingProps;
49 changes: 49 additions & 0 deletions packages/big-design/src/components/Typography/spec.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -146,3 +146,52 @@ test('All typography components allow changing their color given a color prop',
expect(child).toHaveStyle(`color: ${theme.colors.primary}`);
});
});

test('Headings can change their tag', () => {
const { container } = render(<H2 as="h1">Test</H2>);

expect(container.querySelector('h1')).toBeInTheDocument();
});

test('Headings can not change their tag to non-heading tags', () => {
// @ts-ignore
const { container } = render(<H2 as="p">Test</H2>);

expect(container.querySelector('p')).not.toBeInTheDocument();
expect(container.querySelector('h2')).toBeInTheDocument();
});

test('Text and Small can change their tag', () => {
const { container } = render(
<>
<Text as="span">Some Text</Text>
<Small as="span">Some Text</Small>
</>,
);

expect(container.querySelectorAll('span').length).toBe(2);
});

test('Text and Small accept text modifiers', () => {
const { getByTestId } = render(
<>
<Text bold italic underline data-testid="text">
Some Text
</Text>
<Small strikethrough data-testid="small">
Some Text
</Small>
</>,
);

const text = getByTestId('text');
const small = getByTestId('small');

expect(text).toHaveStyle(`
font-weight: 600;
font-style: italic;
text-decoration: underline;
`);

expect(small).toHaveStyle('text-decoration: line-through');
});
34 changes: 31 additions & 3 deletions packages/big-design/src/components/Typography/styled.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ import styled, { css } from 'styled-components';

import { withMargins } from '../../mixins';

import { HeadingProps, SmallProps, TextProps, TypographyProps } from './Typography';
import { HeadingProps, TextProps, TypographyProps } from './types';

const commonTextStyles = (props: TypographyProps) => css`
color: ${({ theme }) => (props.color ? theme.colors[props.color] : theme.colors.secondary70)};
Expand All @@ -13,6 +13,32 @@ const commonTextStyles = (props: TypographyProps) => css`
${props.ellipsis && ellipsis()};
`;

const textModifiers = (props: TextProps) => css`
${({ theme }) =>
props.bold &&
css`
font-weight: ${theme.typography.fontWeight.semiBold};
`}
${() =>
props.italic &&
css`
font-style: italic;
`}
${() =>
props.underline &&
css`
text-decoration: underline;
`}
${() =>
props.strikethrough &&
css`
text-decoration: line-through;
`}
`;

export const StyledH0 = styled.h1<HeadingProps>`
${props => commonTextStyles(props)};
font-size: ${({ theme }) => theme.typography.fontSize.xxxLarge};
Expand Down Expand Up @@ -58,10 +84,11 @@ export const StyledH4 = styled.h4<HeadingProps>`
`;

export const StyledText = styled.p<TextProps>`
${props => commonTextStyles(props)};
${props => commonTextStyles(props)}
font-size: ${({ theme }) => theme.typography.fontSize.medium};
font-weight: ${({ theme }) => theme.typography.fontWeight.regular};
line-height: ${({ theme }) => theme.lineHeight.medium};
${props => textModifiers(props)}
&:last-child {
margin-bottom: 0;
Expand All @@ -70,13 +97,14 @@ export const StyledText = styled.p<TextProps>`
${withMargins()};
`;

export const StyledSmall = styled.p<SmallProps>`
export const StyledSmall = styled.p<TextProps>`
${props => commonTextStyles(props)};
color: ${({ color, theme }) => (color ? theme.colors[color] : theme.colors.secondary60)};
font-size: ${({ theme }) => theme.typography.fontSize.small};
font-weight: ${({ theme }) => theme.typography.fontWeight.regular};
line-height: ${({ theme }) => theme.lineHeight.small};
margin: 0 0 ${({ theme }) => theme.spacing.small};
${props => textModifiers(props)}
&:last-child {
margin-bottom: 0;
Expand Down
54 changes: 54 additions & 0 deletions packages/big-design/src/components/Typography/types.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,54 @@
import { Colors, ThemeInterface } from '@bigcommerce/big-design-theme';
import React from 'react';

import { MarginProps } from '../../mixins';

export interface TypographyProps {
color?: keyof Colors;
ellipsis?: boolean;
theme?: ThemeInterface;
}

export interface TextModifiers {
bold?: boolean;
italic?: boolean;
strikethrough?: boolean;
underline?: boolean;
}

export type HeadingTag = 'h1' | 'h2' | 'h3' | 'h4' | 'h5' | 'h6';
export type TextTag =
| 'abbr'
| 'b'
| 'bdi'
| 'bdo'
| 'blockquote'
| 'caption'
| 'cite'
| 'code'
| 'em'
| 'figcaption'
| 'i'
| 'label'
| 'legend'
| 'p'
| 'pre'
| 'q'
| 's'
| 'small'
| 'span'
| 'strong'
| 'title';

export type TextProps = React.HTMLAttributes<HTMLParagraphElement> &
MarginProps &
TypographyProps &
TextModifiers & {
as?: TextTag;
};

export type HeadingProps = React.HTMLAttributes<HTMLHeadingElement> &
MarginProps &
TypographyProps & {
as?: HeadingTag;
};
48 changes: 48 additions & 0 deletions packages/docs/PropTables/TypographyPropTable.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,54 @@ const typographyProps: Prop[] = [
},
];

const headingProps: Prop[] = [
{
name: 'as',
types: ['h1', 'h2', 'h3', 'h4', 'h5', 'h6'],
description: 'Changes the rendered tag for semantic purposes.',
},
];

const textProps: Prop[] = [
{
name: 'as',
types: 'string',
description: 'Changes the rendered tag for semantic purposes.',
},
{
name: 'bold',
types: 'boolean',
defaultValue: 'false',
description: 'Changes text style to bold.',
},
{
name: 'italic',
types: 'boolean',
defaultValue: 'false',
description: 'Changes text style to italic.',
},
{
name: 'strikethrough',
types: 'boolean',
defaultValue: 'false',
description: 'Changes text style to strikethrough.',
},
{
name: 'underline',
types: 'boolean',
defaultValue: 'false',
description: 'Changes text style to underline.',
},
];

export const TypographyPropTable: React.FC<PropTableWrapper> = props => (
<PropTable title="Typography" propList={typographyProps} {...props} />
);

export const HeadingPropTable: React.FC<PropTableWrapper> = props => (
<PropTable title="Heading" propList={headingProps} {...props} />
);

export const TextPropTable: React.FC<PropTableWrapper> = props => (
<PropTable title="Text" propList={textProps} {...props} />
);
Loading

0 comments on commit f64c4b4

Please sign in to comment.