Skip to content

Commit

Permalink
feat(PageFeatures): Allow the PageFeatures component to render in a g…
Browse files Browse the repository at this point in the history
…rid (#1312)
  • Loading branch information
judahtanthony authored Jan 27, 2021
1 parent ad53b03 commit 4532a1e
Show file tree
Hide file tree
Showing 5 changed files with 185 additions and 59 deletions.
64 changes: 39 additions & 25 deletions packages/gamut-labs/src/landingPage/Feature.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -36,38 +36,60 @@ const FeatureBlock = styled.div`
}
`;

type FeaturedMediaProps = {
featuresMedia?: 'image' | 'icon' | 'stat' | 'none';
type FeaturedImageProps = {
featuresMedia: 'image';
imgSrc: string;
imgAlt?: string;
statText?: string;
imgAlt: string;
};

const FeaturedMedia: React.FC<FeaturedMediaProps> = ({
featuresMedia = 'none',
...rest
}) => {
if (featuresMedia === 'image') {
type FeaturedIconProps = {
featuresMedia: 'icon';
imgSrc: string;
imgAlt: string;
};

type FeaturedStatProps = {
featuresMedia: 'stat';
statText: string;
};

type FeaturedNoMediaProps = {
featuresMedia: 'none';
};

type FeaturedMediaProps =
| FeaturedImageProps
| FeaturedIconProps
| FeaturedStatProps
| FeaturedNoMediaProps;

const FeaturedMedia: React.FC<FeaturedMediaProps> = (props) => {
if (props.featuresMedia === 'image') {
return (
<Image src={rest.imgSrc} alt={rest.imgAlt} data-testid="feature-image" />
<Image
src={props.imgSrc}
alt={props.imgAlt}
data-testid="feature-image"
/>
);
}

if (featuresMedia === 'icon') {
if (props.featuresMedia === 'icon') {
return (
<Icon src={rest.imgSrc} alt={rest.imgAlt} data-testid="feature-icon" />
<Icon src={props.imgSrc} alt={props.imgAlt} data-testid="feature-icon" />
);
}

if (featuresMedia === 'stat') {
if (props.featuresMedia === 'stat') {
return (
<Text
as="div"
marginTop={48}
fontSize={{ xs: 44, lg: 64 }}
fontWeight="title"
data-testid="feature-stat"
>
{rest.statText}
{props.statText}
</Text>
);
}
Expand All @@ -82,24 +104,16 @@ export type FeatureProps = Pick<
FeaturedMediaProps;

export const Feature: React.FC<FeatureProps> = ({
featuresMedia,
imgSrc,
imgAlt = '',
statText,
title,
desc,
onAnchorClick,
testId,
...featuredMediaProps
}) => (
<FeatureBlock data-testid={testId}>
<FeaturedMedia
featuresMedia={featuresMedia}
imgSrc={imgSrc}
imgAlt={imgAlt}
statText={statText}
/>
<FeaturedMedia {...featuredMediaProps} />
{title && (
<Text as="h3" fontSize={{ xs: 22, lg: 26 }}>
<Text as="h3" fontSize={{ xs: 22, lg: 26 }} fontWeight="title">
{title}
</Text>
)}
Expand Down
85 changes: 71 additions & 14 deletions packages/gamut-labs/src/landingPage/PageFeatures.tsx
Original file line number Diff line number Diff line change
@@ -1,7 +1,13 @@
import { Container } from '@codecademy/gamut';
import {
Column,
ColumnSizes,
Container,
LayoutGrid,
ResponsiveProperty,
} from '@codecademy/gamut';
import { mediaQueries } from '@codecademy/gamut-styles';
import styled from '@emotion/styled';
import React from 'react';
import React, { ReactNode } from 'react';

import { CTA, Description, Feature, FeatureProps, Title } from './';
import { BaseProps } from './types';
Expand All @@ -13,6 +19,8 @@ const FlexContainer = styled(Container)`
`;

export type PageFeaturesProps = BaseProps & {
maxCols?: 1 | 2 | 3 | 4;

/**
* Array of features, which consist of image, image alt, title, and description
*/
Expand All @@ -29,10 +37,56 @@ export type PageFeaturesProps = BaseProps & {
featuresMedia?: 'image' | 'icon' | 'stat' | 'none';
};

const rowRenderEach = (
items: FeatureProps[],
itemRenderer: (item: FeatureProps) => ReactNode
): ReactNode => (
<FlexContainer nowrap column>
{items.map(itemRenderer)}
</FlexContainer>
);

const gridRenderEach = (
maxCols: NonNullable<PageFeaturesProps['maxCols']>,
items: FeatureProps[],
itemRenderer: (item: FeatureProps) => ReactNode
): ReactNode => {
const size = { xs: 12, sm: 12 / maxCols } as ResponsiveProperty<ColumnSizes>;
/* eslint-disable react/no-array-index-key */
return (
<LayoutGrid
columnGap={{ lg: 'lg', xs: 'sm' }}
rowGap={{ lg: 'lg', xs: 'sm' }}
>
{items.map((item, i) => (
<Column key={i} size={size}>
{itemRenderer(item)}
</Column>
))}
</LayoutGrid>
);
/* eslint-enable react/no-array-index-key */
};

const renderEach = (
maxCols: PageFeaturesProps['maxCols'],
items: FeatureProps[],
itemRenderer: (item: FeatureProps) => ReactNode
): ReactNode => {
if (maxCols === undefined) {
return rowRenderEach(items, itemRenderer);
}
if (maxCols > 0 && maxCols <= 4) {
return gridRenderEach(maxCols, items, itemRenderer);
}
return null;
};

export const PageFeatures: React.FC<PageFeaturesProps> = ({
title,
desc,
cta,
maxCols,
features,
featuresMedia,
isIcon,
Expand All @@ -49,17 +103,20 @@ export const PageFeatures: React.FC<PageFeaturesProps> = ({
</CTA>
)}
</div>
<FlexContainer nowrap column>
{features.map((feature) => (
<Feature
key={feature.title}
{...feature}
featuresMedia={
featuresMedia ? featuresMedia : isIcon ? 'icon' : 'image'
}
onAnchorClick={onAnchorClick}
/>
))}
</FlexContainer>
{renderEach(
maxCols,
features.map((feature) => ({
...feature,
featuresMedia: featuresMedia
? featuresMedia
: isIcon
? 'icon'
: 'image',
onAnchorClick,
})) as FeatureProps[],
(feature) => (
<Feature key={feature.title} {...feature} />
)
)}
</div>
);
43 changes: 27 additions & 16 deletions packages/gamut-labs/src/landingPage/__tests__/Feature-test.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -3,35 +3,38 @@ import React from 'react';

import { Feature, FeatureProps } from '..';

const renderComponent = (overrides: Partial<FeatureProps> = {}) => {
const props: FeatureProps = {
featuresMedia: 'image',
imgSrc: 'https://content.codecademy.com/courses/free/boba.svg',
imgAlt: 'Codey Boba Tea',
...overrides,
};

return mount(<Feature {...props} />);
};
const renderComponent = (props: FeatureProps) => mount(<Feature {...props} />);

describe('Feature', () => {
it('renders a title when title prop is provided', () => {
const wrapper = renderComponent({ title: 'Test Title' });
const wrapper = renderComponent({
featuresMedia: 'none',
title: 'Test Title',
});
expect(wrapper.find('h3').text()).toEqual('Test Title');
});

it('does not render a title when title prop is not provided', () => {
const wrapper = renderComponent();
const wrapper = renderComponent({
featuresMedia: 'none',
desc: 'Test Description',
});
expect(wrapper.find('h3')).toHaveLength(0);
});

it('renders a description when desc prop is provided', () => {
const wrapper = renderComponent({ desc: 'Test Description' });
const wrapper = renderComponent({
featuresMedia: 'none',
desc: 'Test Description',
});
expect(wrapper.find('p').text()).toEqual('Test Description');
});

it('does not render a description when desc prop is not provided', () => {
const wrapper = renderComponent();
const wrapper = renderComponent({
featuresMedia: 'none',
title: 'Test Title',
});
expect(wrapper.find('p')).toHaveLength(0);
});

Expand All @@ -43,14 +46,22 @@ describe('Feature', () => {
});

it('renders an image', () => {
const wrapper = renderComponent();
const wrapper = renderComponent({
featuresMedia: 'image',
imgSrc: 'https://content.codecademy.com/courses/free/boba.svg',
imgAlt: 'Codey Boba Tea',
});
expect(wrapper.find('img[data-testid="feature-image"]')).toHaveLength(1);
expect(wrapper.find('img[data-testid="feature-icon"]')).toHaveLength(0);
expect(wrapper.find('div[data-testid="feature-stat"]')).toHaveLength(0);
});

it('renders an icon', () => {
const wrapper = renderComponent({ featuresMedia: 'icon' });
const wrapper = renderComponent({
featuresMedia: 'icon',
imgSrc: 'https://content.codecademy.com/courses/free/boba.svg',
imgAlt: 'Codey Boba Tea',
});
expect(wrapper.find('img[data-testid="feature-image"]')).toHaveLength(0);
expect(wrapper.find('img[data-testid="feature-icon"]')).toHaveLength(1);
expect(wrapper.find('div[data-testid="feature-stat"]')).toHaveLength(0);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -66,14 +66,10 @@ describe('PageFeatures', () => {
const wrapper = renderComponent({
features: [
{
imgSrc: 'https://content.codecademy.com/courses/free/boba.svg',
imgAlt: 'Codey boba tea',
title: 'Software Engineer',
desc: '**Software Engineer**. Example link [here](#).',
},
{
imgSrc: 'https://content.codecademy.com/courses/free/boba.svg',
imgAlt: 'Codey boba tea',
title: 'Data Scientist',
desc: '**Data Scientist**. Example link [here](#).',
},
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -136,3 +136,51 @@ Promote a single stat by setting the `featuresMedia` prop to `stat` and providin
{(args) => <PageFeatures {...args} />}
</Story>
</Canvas>

## Wrap the features to create a grid

If you specify a `maxCols` the features at desktop will wrap at that number of columns

<Canvas>
<Story
name="Features Grid"
args={{
features: [
{
imgSrc: 'https://content.codecademy.com/courses/free/boba.svg',
imgAlt: 'Codey boba tea',
title: 'Software Engineer',
desc: '**Software Engineer**. Example link [here](#).',
},
{
imgSrc: 'https://content.codecademy.com/courses/free/boba.svg',
imgAlt: 'Codey boba tea',
title: 'Data Scientist',
desc: '**Data Scientist**. Example link [here](#).',
},
{
imgSrc: 'https://content.codecademy.com/courses/free/boba.svg',
imgAlt: 'Codey boba tea',
title: 'Product Manager',
desc: '**Product Manager**. Example link [here](#).',
},
{
imgSrc: 'https://content.codecademy.com/courses/free/boba.svg',
imgAlt: 'Codey boba tea',
title: 'Product Designer',
desc: '**Product Designer**. Example link [here](#).',
},
{
imgSrc: 'https://content.codecademy.com/courses/free/boba.svg',
imgAlt: 'Codey boba tea',
title: 'Product Designer',
desc: '**Product Designer**. Example link [here](#).',
},
],
featuresMedia: 'icon',
maxCols: 3,
}}
>
{(args) => <PageFeatures {...args} />}
</Story>
</Canvas>

0 comments on commit 4532a1e

Please sign in to comment.