Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

DSD-1521: Add sizeBasedOn prop to Image #1465

Merged
merged 10 commits into from
Dec 6, 2023
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ Currently, this repo is in Prerelease. When it is released, this project will ad
### Adds

- Adds the `useDSHeading` hook to render a default H2 heading or a custom heading element.
- Adds the `sizeBasedOn` prop to the `Image` component.
- Adds the `isDarkBackgroundImage` prop to the `Hero` component.

### Updates
Expand Down
26 changes: 21 additions & 5 deletions src/components/Image/Image.mdx
Original file line number Diff line number Diff line change
@@ -1,16 +1,17 @@
import { ArgTypes, Canvas, Description, Meta, Source } from "@storybook/blocks";

import { changelogData } from "./imageChangelogData";
import ComponentChangelogTable from "../../utils/ComponentChangelogTable";
import * as ImageStories from "./Image.stories";
import Link from "../Link/Link";

<Meta of={ImageStories} />

# Image

| Component Version | DS Version |
| ----------------- | ---------- |
| Added | `0.0.6` |
| Latest | `2.0.0` |
| Component Version | DS Version |
| ----------------- | ------------ |
| Added | `0.0.6` |
| Latest | `Prerelease` |

## Table of Contents

Expand All @@ -23,6 +24,7 @@ import Link from "../Link/Link";
- {<Link href="#types" target="_self">Types</Link>}
- {<Link href="#html-attributes" target="_self">HTML Attributes</Link>}
- {<Link href="#lazy-loading" target="_self">Lazy Loading</Link>}
- {<Link href="#changelog" target="_self">Changelog</Link>}

## Overview

Expand Down Expand Up @@ -62,8 +64,18 @@ then an `img` element will be rendered with or without wrapper style divs.

Use the `size` prop to set the desired size with the `ImageSizes` enum.

In addition, the `sizeBasedOn` prop can be passed to determine whether the size
of the `Image` is updated based on either the `"height"` or `"width"`. By default,
the `sizeBasedOn` prop is set to `"width"`.

### Size Based On Width

<Canvas of={ImageStories.Sizes} />

### Size Based On Height

<Canvas of={ImageStories.SizesBasedOnHeight} />
jackiequach marked this conversation as resolved.
Show resolved Hide resolved

## Aspect Ratios

Use the `aspectRatio` prop to set the desired aspect ratio. Note: the
Expand Down Expand Up @@ -130,3 +142,7 @@ Resources:
- [Browser-level image lazy-loading for the web](https://web.dev/browser-level-image-lazy-loading/)

<Canvas of={ImageStories.LazyLoading} />

## Changelog

<ComponentChangelogTable changelogData={changelogData} />
143 changes: 71 additions & 72 deletions src/components/Image/Image.stories.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ import Image, {
imageTypesArray,
} from "./Image";
import SimpleGrid from "../Grid/SimpleGrid";
import { dimensionTypeArray } from "../../helpers/types";

const meta: Meta<typeof Image> = {
title: "Components/Media & Icons/Image",
Expand Down Expand Up @@ -43,6 +44,11 @@ const meta: Meta<typeof Image> = {
options: imageSizesArray,
table: { defaultValue: { summary: "default" } },
},
sizeBasedOn: {
control: { type: "radio" },
options: dimensionTypeArray,
table: { defaultValue: { summary: "width" } },
},
src: {
description:
"The src attribute is required, and contains the path to the image you want to embed.",
Expand All @@ -53,6 +59,66 @@ const meta: Meta<typeof Image> = {
export default meta;
type Story = StoryObj<typeof Image>;

const imageRow = (opts: any = {}) => {
// We'll use this setup function to render all the images in a list item.
// Some images display better with a dark background.
const styles: any = { textAlign: "center" };
const { size = "large", displayValue, sizeBasedOn = "width", id } = opts;
if (sizeBasedOn === "width" && size === "default") {
styles.width = "100%";
} else if (sizeBasedOn === "height" && size === "default") {
styles.width = "100%";
styles.height = "100%";
}

return (
<Box style={styles} key={id}>
<Heading id={id} level="h4" size="heading6" text={size} />
<Image
alt="Alt text"
caption={displayValue}
size={size}
sizeBasedOn={sizeBasedOn}
src="//placekitten.com/400/300"
/>
</Box>
);
};

const allVStack = (items) => {
return <VStack spacing="l">{items}</VStack>;
};

const imageSizeValues = [
{ size: "xxxsmall", display: "32px", id: "ExtraExtraExtraSmall" },
{ size: "xxsmall", display: "64px", id: "ExtraExtraSmall" },
{ size: "xsmall", display: "96px", id: "ExtraSmall" },
{ size: "small", display: "165px", id: "Small" },
{ size: "medium", display: "225px", id: "Medium" },
{ size: "large", display: "360px", id: "Large" },
{ size: "default", display: "100%", id: "Default" },
];

const sizes = [];
const sizesBasedOnHeight = [];

for (const imageValue of imageSizeValues) {
sizes.push(
imageRow({
size: imageValue.size,
displayValue: imageValue.display,
id: `${imageValue.id}-width`,
})
);
sizesBasedOnHeight.push(
imageRow({
size: imageValue.size,
displayValue: imageValue.display,
id: `${imageValue.id}-height`,
sizeBasedOn: "height",
})
);
}
/**
* Main Story for the Image component. This must contains the `args`
* and `parameters` properties in this object.
Expand All @@ -70,6 +136,7 @@ export const WithControls: Story = {
credit: "Image credit",
imageType: "default",
size: "medium",
sizeBasedOn: "width",
src: "//placekitten.com/400/300",
},
render: (args) => <Image {...args} />,
Expand Down Expand Up @@ -105,78 +172,10 @@ export const FigureAndFigcaption: Story = {
render: (args) => <Image {...args} />,
};
export const Sizes: Story = {
render: () => (
<VStack spacing="l">
<Box textAlign="center">
<Heading
id="ExtraExtraExtraSmall"
level="h4"
size="heading6"
text="xxxsmall"
/>
<Image
alt="Alt text"
caption="32px"
size="xxxsmall"
src="//placekitten.com/400/300"
/>
</Box>
<Box textAlign="center">
<Heading
id="ExtraExtraSmall"
level="h4"
size="heading6"
text="xxsmall"
/>
<Image
alt="Alt text"
caption="64px"
size="xxsmall"
src="//placekitten.com/400/300"
/>
</Box>
<Box textAlign="center">
<Heading id="ExtraSmall" level="h4" size="heading6" text="xsmall" />
<Image
alt="Alt text"
caption="96px"
size="xsmall"
src="//placekitten.com/400/300"
/>
</Box>
<Box textAlign="center">
<Heading id="Small" level="h4" size="heading6" text="small" />
<Image
alt="Alt text"
caption="165px"
size="small"
src="//placekitten.com/400/300"
/>
</Box>
<Box textAlign="center">
<Heading id="Medium" level="h4" size="heading6" text="medium" />
<Image
alt="Alt text"
caption="225px"
size="medium"
src="//placekitten.com/400/300"
/>
</Box>
<Box textAlign="center">
<Heading id="Large" level="h4" size="heading6" text="large" />
<Image
alt="Alt text"
caption="360px"
size="large"
src="//placekitten.com/400/300"
/>
</Box>
<Box textAlign="center" width="100%">
<Heading id="Default" level="h4" size="heading6" text="default" />
<Image alt="Alt text" caption="100%" src="//placekitten.com/400/300" />
</Box>
</VStack>
),
render: () => allVStack(sizes),
};
export const SizesBasedOnHeight: Story = {
render: () => allVStack(sizesBasedOnHeight),
};

const imageBlockStyles = {
Expand Down
4 changes: 4 additions & 0 deletions src/components/Image/Image.test.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -119,6 +119,9 @@ describe("Image", () => {
const sizeLarge = renderer
.create(<Image src="test.png" alt="" size="large" />)
.toJSON();
const sizeBasedOnHeight = renderer
.create(<Image src="test.png" alt="" size="large" sizeBasedOn="height" />)
.toJSON();
const ratioFourByThree = renderer
.create(<Image src="test.png" alt="" aspectRatio="fourByThree" />)
.toJSON();
Expand Down Expand Up @@ -170,6 +173,7 @@ describe("Image", () => {
expect(sizeSmall).toMatchSnapshot();
expect(sizeMedium).toMatchSnapshot();
expect(sizeLarge).toMatchSnapshot();
expect(sizeBasedOnHeight).toMatchSnapshot();
expect(ratioFourByThree).toMatchSnapshot();
expect(ratioOneByTwo).toMatchSnapshot();
expect(ratioOriginal).toMatchSnapshot();
Expand Down
9 changes: 9 additions & 0 deletions src/components/Image/Image.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ import {
import React, { forwardRef, ImgHTMLAttributes } from "react";
import { useInView } from "react-intersection-observer";
import HelperErrorText from "../HelperErrorText/HelperErrorText";
import { DimensionTypes } from "../../helpers/types";

export const imageRatiosArray = [
"fourByThree",
Expand Down Expand Up @@ -67,6 +68,8 @@ interface ImageWrapperProps {
aspectRatio?: ImageRatios;
/** Optional value to control the size of the image */
size?: ImageSizes;
/** Sets the image size based on the width or height. Width by default. */
sizeBasedOn?: DimensionTypes;
}

export interface ImageProps
Expand Down Expand Up @@ -100,11 +103,13 @@ const ImageWrapper = chakra(
children,
aspectRatio = "original",
size = "default",
sizeBasedOn = "width",
...rest
} = props;
const styles = useMultiStyleConfig("CustomImageWrapper", {
ratio: aspectRatio,
size,
sizeBasedOn,
});
return (
<Box
Expand Down Expand Up @@ -135,6 +140,7 @@ export const Image = chakra(
imageType = "default",
isLazy = false,
size = "default",
sizeBasedOn = "width",
src,
...rest
} = props;
Expand All @@ -149,7 +155,9 @@ export const Image = chakra(
const useImageWrapper = aspectRatio !== "original";
const styles = useMultiStyleConfig("CustomImage", {
variant: imageType,
ratio: aspectRatio,
size,
sizeBasedOn,
});
let imageComponent: JSX.Element | null = null;
let lazyRef = undefined;
Expand Down Expand Up @@ -196,6 +204,7 @@ export const Image = chakra(
aspectRatio={aspectRatio}
className={className}
size={size}
sizeBasedOn={sizeBasedOn}
{...(caption || credit ? {} : rest)}
>
{imageComponent}
Expand Down
Loading
Loading