Skip to content

Commit 75be8bf

Browse files
authored
Merge pull request #1671 from dxc-technology/gomezivann/image
First version of the Image component
2 parents 125f0b8 + 76cb8e3 commit 75be8bf

File tree

10 files changed

+510
-1
lines changed

10 files changed

+510
-1
lines changed

lib/src/common/variables.ts

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -490,6 +490,9 @@ export const componentTokens = {
490490
level5LineHeight: CORE_TOKENS.type_leading_normal,
491491
level5LetterSpacing: CORE_TOKENS.type_spacing_wide_01,
492492
},
493+
image: {
494+
captionFontColor: CORE_TOKENS.color_grey_900,
495+
},
493496
link: {
494497
fontColor: CORE_TOKENS.color_blue_800,
495498
fontFamily: CORE_TOKENS.inherit,

lib/src/image/Image.stories.tsx

Lines changed: 127 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,127 @@
1+
import React from "react";
2+
import DxcImage from "./Image";
3+
import Title from "../../.storybook/components/Title";
4+
import ExampleContainer from "../../.storybook/components/ExampleContainer";
5+
import { DxcFlex, DxcInset, DxcParagraph } from "../main";
6+
7+
export default {
8+
title: "Image",
9+
component: DxcImage,
10+
};
11+
12+
export const Chromatic = () => (
13+
<>
14+
<Title title="Image component" theme="light" level={2} />
15+
<ExampleContainer>
16+
<Title title="Simple image" theme="light" level={4} />
17+
<DxcImage
18+
alt="Example image"
19+
width="100%"
20+
src="https://images.ctfassets.net/hrltx12pl8hq/5596z2BCR9KmT1KeRBrOQa/4070fd4e2f1a13f71c2c46afeb18e41c/shutterstock_451077043-hero1.jpg"
21+
/>
22+
</ExampleContainer>
23+
<ExampleContainer>
24+
<Title title="Image with text" theme="light" level={4} />
25+
<DxcParagraph>
26+
Lorem ipsum dolor sit amet consectetur. Tincidunt sed pharetra mollis duis volutpat urna. Hendrerit aliquet et
27+
arcu purus. Sodales elementum sollicitudin consequat elementum tortor. Lectus eget cursus ut ac pharetra
28+
lobortis integer eu. Potenti amet ac id risus ac nunc orci nibh. Tempus vitae vitae aenean arcu. Nibh tristique
29+
porta dui enim eget tristique rutrum. Quisque faucibus suscipit nibh est sed. Netus venenatis congue diam in dui
30+
morbi dignissim lorem. Urna aliquet sem in tincidunt. Nunc arcu nec fringilla enim purus ut justo nisi. Vel mus
31+
ut ornare faucibus blandit diam sit vestibulum massa. Semper nullam sit sagittis hendrerit augue. In fermentum
32+
metus proin arcu faucibus proin nibh sit. Vel integer sed enim in sed vel nec ut vitae. Commodo sagittis
33+
volutpat id lorem.
34+
</DxcParagraph>
35+
<DxcInset top="2rem" bottom="2rem">
36+
<DxcImage
37+
alt="Ratatouille is a great movie"
38+
caption="Ratatouille with a smile on his face."
39+
src="https://hips.hearstapps.com/es.h-cdn.co/fotoes/images/cinefilia/por-que-ratatouille-nos-sigue-enamorando-10-anos-despues/136444706-1-esl-ES/Por-que-Ratatouille-nos-sigue-enamorando-10-anos-despues.jpg"
40+
/>
41+
</DxcInset>
42+
<DxcParagraph>
43+
Lorem ipsum dolor sit amet consectetur. Tincidunt sed pharetra mollis duis volutpat urna. Hendrerit aliquet et
44+
arcu purus. Sodales elementum sollicitudin consequat elementum tortor. Lectus eget cursus ut ac pharetra
45+
lobortis integer eu. Potenti amet ac id risus ac nunc orci nibh. Tempus vitae vitae aenean arcu. Nibh tristique
46+
porta dui enim eget tristique rutrum. Quisque faucibus suscipit nibh est sed. Netus venenatis congue diam in dui
47+
morbi dignissim lorem. Urna aliquet sem in tincidunt. Nunc arcu nec fringilla enim purus ut justo nisi. Vel mus
48+
ut ornare faucibus blandit diam sit vestibulum massa. Semper nullam sit sagittis hendrerit augue. In fermentum
49+
metus proin arcu faucibus proin nibh sit. Vel integer sed enim in sed vel nec ut vitae. Commodo sagittis
50+
volutpat id lorem.
51+
</DxcParagraph>
52+
</ExampleContainer>
53+
<ExampleContainer>
54+
<Title title="Example image" theme="light" level={4} />
55+
<DxcFlex gap="1rem">
56+
<DxcImage
57+
alt="Camera pic"
58+
caption="Picture of a camera and the sunset."
59+
width="500px"
60+
src="https://assets.entrepreneur.com/content/3x2/2000/20191009140007-GettyImages-1053962188.jpeg"
61+
/>
62+
<DxcParagraph>
63+
Lorem ipsum dolor sit amet consectetur. Tincidunt sed pharetra mollis duis volutpat urna. Hendrerit aliquet et
64+
arcu purus. Sodales elementum sollicitudin consequat elementum tortor. Lectus eget cursus ut ac pharetra
65+
lobortis integer eu. Potenti amet ac id risus ac nunc orci nibh. Tempus vitae vitae aenean arcu. Nibh
66+
tristique porta dui enim eget tristique rutrum. Quisque faucibus suscipit nibh est sed. Netus venenatis congue
67+
diam in dui morbi dignissim lorem. Urna aliquet sem in tincidunt. Nunc arcu nec fringilla enim purus ut justo
68+
nisi. Vel mus ut ornare faucibus blandit diam sit vestibulum massa. Semper nullam sit sagittis hendrerit
69+
augue. In fermentum metus proin arcu faucibus proin nibh sit. Vel integer sed enim in sed vel nec ut vitae.
70+
Commodo sagittis volutpat id lorem. Lorem ipsum dolor sit amet consectetur. Tincidunt sed pharetra mollis duis
71+
volutpat urna. Hendrerit aliquet et arcu purus. Sodales elementum sollicitudin consequat elementum tortor.
72+
Lectus eget cursus ut ac pharetra lobortis integer eu. Potenti amet ac id risus ac nunc orci nibh. Tempus
73+
vitae vitae aenean arcu. Nibh tristique porta dui enim eget tristique rutrum. Quisque faucibus suscipit nibh
74+
est sed. Netus venenatis congue diam in dui morbi dignissim lorem. Urna aliquet sem in tincidunt. Nunc arcu
75+
nec fringilla enim purus ut justo nisi. Vel mus ut ornare faucibus blandit diam sit vestibulum massa. Semper
76+
nullam sit sagittis hendrerit augue. In fermentum metus proin arcu faucibus proin nibh sit. Vel integer sed
77+
enim in sed vel nec ut vitae. Commodo sagittis volutpat id lorem.
78+
</DxcParagraph>
79+
</DxcFlex>
80+
</ExampleContainer>
81+
<ExampleContainer>
82+
<Title title="Object position" theme="light" level={4} />
83+
<DxcImage
84+
alt="Moon pic"
85+
caption="Picture of the moon."
86+
width="250px"
87+
src="https://interactive-examples.mdn.mozilla.net/media/examples/moon.jpg"
88+
objectPosition="right top"
89+
objectFit="none"
90+
/>
91+
</ExampleContainer>
92+
<ExampleContainer>
93+
<Title title="Object fit: contain" theme="light" level={4} />
94+
<div style={{ display: "flex", width: "fit-content", border: "1px solid #000", padding: "0.5rem" }}>
95+
<DxcImage
96+
alt="Dog pic"
97+
src="https://cc-prod.scene7.com/is/image/CCProdAuthor/What-is-Stock-Photography_P1_mobile?$pjpeg$&jpegSize=200&wid=720"
98+
width="200px"
99+
height="200px"
100+
objectFit="contain"
101+
caption="Pretty dog."
102+
/>
103+
</div>
104+
</ExampleContainer>
105+
<ExampleContainer>
106+
<Title title="Object fit: cover" theme="light" level={4} />
107+
<div style={{ width: "75%", height: "300px" }}>
108+
<DxcImage
109+
alt="Spaceship pic"
110+
src="https://media.istockphoto.com/id/1344443930/es/foto/lanzamiento-de-cohetes-del-transbordador-espacial-en-el-cielo-y-nubes-al-espacio-exterior.jpg?s=612x612&w=0&k=20&c=CO2A96GnnWvJsgZuj9WfYCVIBVzicnQDfnzwD1nomN0="
111+
objectFit="cover"
112+
width="50%"
113+
height="100%"
114+
objectPosition="0px 0px"
115+
/>
116+
<DxcImage
117+
alt="Spaceship pic"
118+
src="https://media.istockphoto.com/id/1344443930/es/foto/lanzamiento-de-cohetes-del-transbordador-espacial-en-el-cielo-y-nubes-al-espacio-exterior.jpg?s=612x612&w=0&k=20&c=CO2A96GnnWvJsgZuj9WfYCVIBVzicnQDfnzwD1nomN0="
119+
objectFit="cover"
120+
width="50%"
121+
height="100%"
122+
objectPosition="0px 0px"
123+
/>
124+
</div>
125+
</ExampleContainer>
126+
</>
127+
);

lib/src/image/Image.tsx

Lines changed: 69 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,69 @@
1+
import React from "react";
2+
import ImagePropsType, { CaptionWrapperProps } from "./types";
3+
import styled, { ThemeProvider } from "styled-components";
4+
import useTheme from "../useTheme";
5+
import BaseTypography from "../utils/BaseTypography";
6+
7+
const CaptionWrapper = ({ condition, wrapper, children }: CaptionWrapperProps): JSX.Element => (
8+
<>{condition ? wrapper(children) : children}</>
9+
);
10+
11+
const DxcImage = ({
12+
alt,
13+
caption,
14+
lazyLoading = false,
15+
src,
16+
srcSet,
17+
sizes,
18+
width,
19+
height,
20+
objectFit,
21+
objectPosition,
22+
onLoad,
23+
onError,
24+
}: ImagePropsType) => {
25+
const colorsTheme = useTheme();
26+
27+
return (
28+
<ThemeProvider theme={colorsTheme.image}>
29+
<CaptionWrapper
30+
condition={caption != undefined}
31+
wrapper={(children: React.ReactNode) => (
32+
<Figure>
33+
{children}
34+
<BaseTypography as="figcaption" color={colorsTheme.image.captionFontColor} fontSize="0.875rem">
35+
{caption}
36+
</BaseTypography>
37+
</Figure>
38+
)}
39+
>
40+
<img
41+
alt={alt}
42+
loading={lazyLoading ? "lazy" : undefined}
43+
onLoad={onLoad}
44+
onError={onError}
45+
src={src}
46+
srcSet={srcSet}
47+
sizes={sizes}
48+
style={{
49+
objectFit,
50+
objectPosition,
51+
width,
52+
height,
53+
}}
54+
/>
55+
</CaptionWrapper>
56+
</ThemeProvider>
57+
);
58+
};
59+
60+
const Figure = styled.figure`
61+
display: flex;
62+
flex-direction: column;
63+
gap: 1rem;
64+
width: fit-content;
65+
margin: 0;
66+
padding: 0;
67+
`;
68+
69+
export default DxcImage;

lib/src/image/types.ts

Lines changed: 73 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,73 @@
1+
type Props = {
2+
/**
3+
* Alternative text description displayed when the specified image is not loaded.
4+
*
5+
* See MDN: https://developer.mozilla.org/en-US/docs/Web/API/HTMLImageElement/alt
6+
* See W3C alt decision tree: https://www.w3.org/WAI/tutorials/images/decision-tree/
7+
*/
8+
alt: string;
9+
/**
10+
* Image legend with a descriptive purpose. It is placed below the image and is complementary to the alt attribute,
11+
* which is required regardless of the presence of the caption or not.
12+
*/
13+
caption?: string;
14+
/**
15+
* If true, the image will be loaded only when it is visible on the screen (lazy loading).
16+
* Otherwise and by default, the image will be loaded as soon as the component is mounted (eager loading).
17+
*/
18+
lazyLoading?: boolean;
19+
/**
20+
* URL of the image. This prop is required and must be valid.
21+
*/
22+
src: string;
23+
/**
24+
* List of one or more strings separated by commas indicating a set of possible images for the user agent to use.
25+
*
26+
* See MDN: https://developer.mozilla.org/en-US/docs/Web/API/HTMLImageElement/srcset
27+
*/
28+
srcSet?: string;
29+
/**
30+
* One or more strings separated by commas, indicating a set of source sizes.
31+
* If the srcSet attribute is absent or contains no values with a width descriptor,
32+
* then this attribute has no effect.
33+
*
34+
* See MDN: https://developer.mozilla.org/en-US/docs/Web/API/HTMLImageElement/sizes
35+
*/
36+
sizes?: string;
37+
/**
38+
* Sets the rendered width of the image.
39+
*/
40+
width?: string;
41+
/**
42+
* Sets the rendered height of the image.
43+
*/
44+
height?: string;
45+
/**
46+
* Sets the object-fit CSS property.
47+
*
48+
* See MDN: https://developer.mozilla.org/en-US/docs/Web/CSS/object-fit
49+
*/
50+
objectFit?: "contain" | "cover" | "fill" | "none" | "scale-down";
51+
/**
52+
* Sets the object-position CSS property.
53+
*
54+
* See MDN: https://developer.mozilla.org/en-US/docs/Web/CSS/object-position
55+
*/
56+
objectPosition?: string;
57+
/**
58+
* This function will be called when the image is loaded.
59+
*/
60+
onLoad?: React.ReactEventHandler<HTMLImageElement>;
61+
/**
62+
* This function will be called when the image fails to load.
63+
*/
64+
onError?: React.ReactEventHandler<HTMLImageElement>;
65+
};
66+
67+
export type CaptionWrapperProps = {
68+
condition: boolean;
69+
wrapper: (children: React.ReactNode) => JSX.Element;
70+
children: React.ReactNode;
71+
};
72+
73+
export default Props;

lib/src/main.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -40,6 +40,7 @@ import DxcTypography from "./typography/Typography";
4040
import DxcParagraph from "./paragraph/Paragraph";
4141
import DxcBulletedList from "./bulleted-list/BulletedList";
4242
import DxcGrid from "./grid/Grid";
43+
import DxcImage from "./image/Image";
4344

4445
import HalstackContext, { HalstackProvider, HalstackLanguageContext } from "./HalstackContext";
4546
import { BackgroundColorProvider } from "./BackgroundColorContext";
@@ -91,4 +92,5 @@ export {
9192
DxcParagraph,
9293
DxcBulletedList,
9394
DxcGrid,
95+
DxcImage,
9496
};

website/pages/components/flex/usage.tsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@ const Usage = () => {
77
return (
88
<>
99
<Head>
10-
<title>Flex Usage— Halstack Design System</title>
10+
<title>Flex Usage — Halstack Design System</title>
1111
</Head>
1212
<FlexUsagePage></FlexUsagePage>
1313
</>
Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
import Head from "next/head";
2+
import type { ReactElement } from "react";
3+
import ImagePageLayout from "../../../screens/components/image/ImagePageLayout";
4+
import ImageCodePage from "../../../screens/components/image/code/ImageCodePage";
5+
6+
const Index = () => {
7+
return (
8+
<>
9+
<Head>
10+
<title>Image — Halstack Design System</title>
11+
</Head>
12+
<ImageCodePage></ImageCodePage>
13+
</>
14+
);
15+
};
16+
17+
Index.getLayout = function getLayout(page: ReactElement) {
18+
return <ImagePageLayout>{page}</ImagePageLayout>;
19+
};
20+
21+
export default Index;

website/screens/common/componentList.js

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,7 @@ exports.componentsList = [
2626
{ label: "Grid", path: "/components/grid", status: "Experimental" },
2727
{ label: "Header", path: "/components/header", status: "Ready" },
2828
{ label: "Heading", path: "/components/heading", status: "Ready" },
29+
{ label: "Image", path: "/components/image", status: "Experimental" },
2930
{ label: "Inset", path: "/components/inset", status: "Ready" },
3031
{ label: "Link", path: "/components/link", status: "Ready" },
3132
{ label: "Nav Tabs", path: "/components/nav-tabs", status: "Ready" },
Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
1+
import { DxcParagraph, DxcFlex } from "@dxc-technology/halstack-react";
2+
import PageHeading from "@/common/PageHeading";
3+
import TabsPageHeading from "@/common/TabsPageLayout";
4+
import ComponentHeading from "@/common/ComponentHeading";
5+
6+
const ImagePageHeading = ({ children }: { children: React.ReactNode }) => {
7+
const tabs = [{ label: "Code", path: "/components/image" }];
8+
9+
return (
10+
<DxcFlex direction="column" gap="3rem">
11+
<PageHeading>
12+
<DxcFlex direction="column" gap="2rem">
13+
<ComponentHeading name="Image" />
14+
<DxcParagraph>
15+
The Image component is used to embed images in Halstack-based user
16+
interfaces.
17+
</DxcParagraph>
18+
<TabsPageHeading tabs={tabs}></TabsPageHeading>
19+
</DxcFlex>
20+
</PageHeading>
21+
{children}
22+
</DxcFlex>
23+
);
24+
};
25+
26+
export default ImagePageHeading;

0 commit comments

Comments
 (0)