Skip to content

Commit

Permalink
Lift ImageDetails To common-rendering (#3520)
Browse files Browse the repository at this point in the history
- Renamed `HeaderImageCaption` to `ImageDetails`
- Simplified component API
- Broke up component CSS
- Added type definitions to common
- Lifted `pipe` and `maybeRender` to common
- Added stories
- Fix credit rendering
  • Loading branch information
JamieB-gu authored Oct 14, 2021
1 parent be2a052 commit 28425a1
Show file tree
Hide file tree
Showing 9 changed files with 197 additions and 157 deletions.
10 changes: 8 additions & 2 deletions apps-rendering/src/components/headerImage.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,17 +2,21 @@

import type { SerializedStyles } from '@emotion/react';
import { css } from '@emotion/react';
import ImageDetails from '@guardian/common-rendering/src/components/imageDetails';
import Img from '@guardian/common-rendering/src/components/img';
import type { Sizes } from '@guardian/common-rendering/src/sizes';
import { remSpace } from '@guardian/src-foundations';
import { from } from '@guardian/src-foundations/mq';
import type { Format } from '@guardian/types';
import { Design, Display, some } from '@guardian/types';
import HeaderImageCaption, { captionId } from 'components/headerImageCaption';
import type { Image } from 'image';
import type { FC } from 'react';
import { wideContentWidth } from 'styles';

// ----- Setup ----- //

const captionId = 'header-image-caption';

// ----- Subcomponents ----- //

interface CaptionProps {
Expand All @@ -26,9 +30,11 @@ const Caption: FC<CaptionProps> = ({ format, image }: CaptionProps) => {
return null;
default:
return (
<HeaderImageCaption
<ImageDetails
caption={image.nativeCaption}
credit={image.credit}
supportsDarkMode
id={captionId}
/>
);
}
Expand Down
122 changes: 0 additions & 122 deletions apps-rendering/src/components/headerImageCaption.tsx

This file was deleted.

30 changes: 1 addition & 29 deletions apps-rendering/src/lib.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
// ----- Imports ----- //

import { maybeRender, pipe } from '@guardian/common-rendering/src/lib';
import type { Option, Result } from '@guardian/types';
import {
err,
Expand All @@ -12,7 +13,6 @@ import {
some,
withDefault,
} from '@guardian/types';
import type { ReactElement } from 'react';

// ----- Functions ----- //

Expand All @@ -21,29 +21,6 @@ const compose =
(a: A): C =>
f(g(a));

function pipe<A, B>(a: A, f: (_a: A) => B): B;
function pipe<A, B, C>(a: A, f: (_a: A) => B, g: (_b: B) => C): C;
function pipe<A, B, C, D>(
a: A,
f: (_a: A) => B,
g: (_b: B) => C,
h: (_c: C) => D,
): D;
function pipe<A, B, C, D>(
a: A,
f: (_a: A) => B,
g?: (_b: B) => C,
h?: (_c: C) => D,
): unknown {
if (g !== undefined && h !== undefined) {
return h(g(f(a)));
} else if (g !== undefined) {
return g(f(a));
}

return f(a);
}

const identity = <A>(a: A): A => a;

// The nodeType for ELEMENT_NODE has the value 1.
Expand Down Expand Up @@ -77,11 +54,6 @@ function errorToString(error: unknown, fallback: string): string {
const isObject = (a: unknown): a is Record<string, unknown> =>
typeof a === 'object' && a !== null;

const maybeRender = <A>(
oa: Option<A>,
f: (a: A) => ReactElement | null,
): ReactElement | null => fold(f, null)(oa);

function handleErrors(response: Response): Response | never {
if (!response.ok) {
throw Error(response.statusText);
Expand Down
1 change: 1 addition & 0 deletions common-rendering/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@
"devDependencies": {
"@emotion/jest": "^11.3.0",
"@types/jest": "^26.0.20",
"@types/react-test-renderer": "^17.0.1",
"jest": "^26.6.3",
"react-test-renderer": "^17.0.1",
"ts-jest": "^26.5.3"
Expand Down
27 changes: 27 additions & 0 deletions common-rendering/src/components/imageDetails.stories.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
// ----- Imports ----- //

import { some } from '@guardian/types';
import type { FC } from 'react';

import ImageDetails from './imageDetails';

// ----- Stories ----- //

const Default: FC = () =>
<ImageDetails
caption={some('A caption')}
credit={some('By a person')}
supportsDarkMode
id="caption-id"
/>

// ----- Exports ----- //

export default {
component: ImageDetails,
title: 'Common/Components/ImageDetails',
}

export {
Default,
}
Original file line number Diff line number Diff line change
@@ -1,16 +1,25 @@
// ----- Imports ----- //

import { matchers } from '@emotion/jest';
import { some } from '@guardian/types';
import HeaderImageCaption, { captionId } from 'components/headerImageCaption';
import ImageDetails from './imageDetails';
import renderer from 'react-test-renderer';

// ----- Setup ----- //

expect.extend(matchers);
const captionId = 'header-image-caption';

// ----- Tests ----- //

describe('HeaderImageCaption component renders as expected', () => {
it('Formats the Caption correctly', () => {
const headerImageCaption = renderer.create(
<HeaderImageCaption
<ImageDetails
caption={some('Here is a caption.')}
credit={some('Photograph: cameraman')}
supportsDarkMode
id={captionId}
/>,
);

Expand Down
115 changes: 115 additions & 0 deletions common-rendering/src/components/imageDetails.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,115 @@
// ----- Imports ----- //

import type { SerializedStyles } from '@emotion/react';
import { css } from '@emotion/react';
import { remSpace } from '@guardian/src-foundations';
import { brandAlt, neutral } from '@guardian/src-foundations/palette';
import { textSans } from '@guardian/src-foundations/typography';
import { SvgCamera } from '@guardian/src-icons';
import { Option, OptionKind } from '@guardian/types';
import { withDefault } from '@guardian/types';
import { darkModeCss } from '@guardian/common-rendering/src/lib';
import type { FC } from 'react';

// ----- Component ----- //

const styles = css`
position: absolute;
left: 0;
right: 0;
bottom: 0;
`;

const detailsStyles = (supportsDarkMode: boolean): SerializedStyles => css`
&[open] {
min-height: 44px;
max-height: 999px;
background-color: rgba(0, 0, 0, 0.8);
padding: ${remSpace[3]};
overflow: hidden;
padding-right: ${remSpace[12]};
z-index: 1;
color: ${neutral[100]};
${textSans.small()};
box-sizing: border-box;
${darkModeCss(supportsDarkMode)`
color: ${neutral[60]};
`}
}
`;

const iconStyles = (supportsDarkMode: boolean): SerializedStyles => css`
display: block;
text-align: center;
background-color: ${brandAlt[400]};
width: 34px;
height: 34px;
position: absolute;
bottom: 8px;
right: 8px;
border-radius: 100%;
outline: none;
&::-webkit-details-marker {
display: none;
}
${darkModeCss(supportsDarkMode)`
background-color: ${neutral[60]};
opacity: .7;
`}
`;

const svgStyles: SerializedStyles = css`
line-height: 32px;
font-size: 0;
svg {
width: 75%;
height: 75%;
margin: 12.5%;
}
path {
fill: ${neutral[7]};
}
`;

interface Props {
caption: Option<string>;
credit: Option<string>;
supportsDarkMode: boolean;
id: string;
}

const ImageDetails: FC<Props> = ({
caption,
credit,
supportsDarkMode,
id,
}: Props) => {
if (caption.kind === OptionKind.None && credit.kind === OptionKind.None) {
return null;
}

return (
<figcaption css={styles}>
<details css={detailsStyles(supportsDarkMode)}>
<summary css={iconStyles(supportsDarkMode)}>
<span css={svgStyles}>
<SvgCamera />
Click to see figure caption
</span>
</summary>
<span id={id}>
{withDefault('')(caption)} {withDefault('')(credit)}
</span>
</details>
</figcaption>
);
}

// ----- Exports ----- //

export default ImageDetails;
Loading

0 comments on commit 28425a1

Please sign in to comment.