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

First version of the Image component #1671

Merged
merged 8 commits into from Sep 13, 2023
Merged
Show file tree
Hide file tree
Changes from 6 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
3 changes: 3 additions & 0 deletions lib/src/common/variables.ts
Expand Up @@ -490,6 +490,9 @@ export const componentTokens = {
level5LineHeight: CORE_TOKENS.type_leading_normal,
level5LetterSpacing: CORE_TOKENS.type_spacing_wide_01,
},
image: {
captionFontColor: CORE_TOKENS.color_grey_900,
},
link: {
fontColor: CORE_TOKENS.color_blue_800,
fontFamily: CORE_TOKENS.inherit,
Expand Down
127 changes: 127 additions & 0 deletions lib/src/image/Image.stories.tsx
@@ -0,0 +1,127 @@
import React from "react";
import DxcImage from "./Image";
import Title from "../../.storybook/components/Title";
import ExampleContainer from "../../.storybook/components/ExampleContainer";
import { DxcFlex, DxcInset, DxcParagraph } from "../main";

export default {
title: "Image",
component: DxcImage,
};

export const Chromatic = () => (
<>
<Title title="Image component" theme="light" level={2} />
<ExampleContainer>
<Title title="Simple image" theme="light" level={4} />
<DxcImage
alt="Example image"
width="100%"
src="https://images.ctfassets.net/hrltx12pl8hq/5596z2BCR9KmT1KeRBrOQa/4070fd4e2f1a13f71c2c46afeb18e41c/shutterstock_451077043-hero1.jpg"
/>
</ExampleContainer>
<ExampleContainer>
<Title title="Image with text" theme="light" level={4} />
<DxcParagraph>
Lorem ipsum dolor sit amet consectetur. Tincidunt sed pharetra mollis duis volutpat urna. Hendrerit aliquet et
arcu purus. Sodales elementum sollicitudin consequat elementum tortor. Lectus eget cursus ut ac pharetra
lobortis integer eu. Potenti amet ac id risus ac nunc orci nibh. Tempus vitae vitae aenean arcu. Nibh tristique
porta dui enim eget tristique rutrum. Quisque faucibus suscipit nibh est sed. Netus venenatis congue diam in dui
morbi dignissim lorem. Urna aliquet sem in tincidunt. Nunc arcu nec fringilla enim purus ut justo nisi. Vel mus
ut ornare faucibus blandit diam sit vestibulum massa. Semper nullam sit sagittis hendrerit augue. In fermentum
metus proin arcu faucibus proin nibh sit. Vel integer sed enim in sed vel nec ut vitae. Commodo sagittis
volutpat id lorem.
</DxcParagraph>
<DxcInset top="2rem" bottom="2rem">
<DxcImage
alt="Ratatouille is a great movie"
caption="Ratatouille with a smile on his face."
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"
/>
</DxcInset>
<DxcParagraph>
Lorem ipsum dolor sit amet consectetur. Tincidunt sed pharetra mollis duis volutpat urna. Hendrerit aliquet et
arcu purus. Sodales elementum sollicitudin consequat elementum tortor. Lectus eget cursus ut ac pharetra
lobortis integer eu. Potenti amet ac id risus ac nunc orci nibh. Tempus vitae vitae aenean arcu. Nibh tristique
porta dui enim eget tristique rutrum. Quisque faucibus suscipit nibh est sed. Netus venenatis congue diam in dui
morbi dignissim lorem. Urna aliquet sem in tincidunt. Nunc arcu nec fringilla enim purus ut justo nisi. Vel mus
ut ornare faucibus blandit diam sit vestibulum massa. Semper nullam sit sagittis hendrerit augue. In fermentum
metus proin arcu faucibus proin nibh sit. Vel integer sed enim in sed vel nec ut vitae. Commodo sagittis
volutpat id lorem.
</DxcParagraph>
</ExampleContainer>
<ExampleContainer>
<Title title="Example image" theme="light" level={4} />
<DxcFlex gap="1rem">
<DxcImage
alt="Camera pic"
caption="Picture of a camera and the sunset."
width="500px"
src="https://assets.entrepreneur.com/content/3x2/2000/20191009140007-GettyImages-1053962188.jpeg"
/>
<DxcParagraph>
Lorem ipsum dolor sit amet consectetur. Tincidunt sed pharetra mollis duis volutpat urna. Hendrerit aliquet et
arcu purus. Sodales elementum sollicitudin consequat elementum tortor. Lectus eget cursus ut ac pharetra
lobortis integer eu. Potenti amet ac id risus ac nunc orci nibh. Tempus vitae vitae aenean arcu. Nibh
tristique porta dui enim eget tristique rutrum. Quisque faucibus suscipit nibh est sed. Netus venenatis congue
diam in dui morbi dignissim lorem. Urna aliquet sem in tincidunt. Nunc arcu nec fringilla enim purus ut justo
nisi. Vel mus ut ornare faucibus blandit diam sit vestibulum massa. Semper nullam sit sagittis hendrerit
augue. In fermentum metus proin arcu faucibus proin nibh sit. Vel integer sed enim in sed vel nec ut vitae.
Commodo sagittis volutpat id lorem. Lorem ipsum dolor sit amet consectetur. Tincidunt sed pharetra mollis duis
volutpat urna. Hendrerit aliquet et arcu purus. Sodales elementum sollicitudin consequat elementum tortor.
Lectus eget cursus ut ac pharetra lobortis integer eu. Potenti amet ac id risus ac nunc orci nibh. Tempus
vitae vitae aenean arcu. Nibh tristique porta dui enim eget tristique rutrum. Quisque faucibus suscipit nibh
est sed. Netus venenatis congue diam in dui morbi dignissim lorem. Urna aliquet sem in tincidunt. Nunc arcu
nec fringilla enim purus ut justo nisi. Vel mus ut ornare faucibus blandit diam sit vestibulum massa. Semper
nullam sit sagittis hendrerit augue. In fermentum metus proin arcu faucibus proin nibh sit. Vel integer sed
enim in sed vel nec ut vitae. Commodo sagittis volutpat id lorem.
</DxcParagraph>
</DxcFlex>
</ExampleContainer>
<ExampleContainer>
<Title title="Object position" theme="light" level={4} />
<DxcImage
alt="Moon pic"
caption="Picture of the moon."
width="250px"
src="https://interactive-examples.mdn.mozilla.net/media/examples/moon.jpg"
objectPosition="right top"
objectFit="none"
/>
</ExampleContainer>
<ExampleContainer>
<Title title="Object fit: contain" theme="light" level={4} />
<div style={{ display: "flex", width: "fit-content", border: "1px solid #000", padding: "0.5rem" }}>
<DxcImage
alt="Dog pic"
src="https://cc-prod.scene7.com/is/image/CCProdAuthor/What-is-Stock-Photography_P1_mobile?$pjpeg$&jpegSize=200&wid=720"
width="200px"
height="200px"
objectFit="contain"
caption="Pretty dog."
/>
</div>
</ExampleContainer>
<ExampleContainer>
<Title title="Object fit: cover" theme="light" level={4} />
<div style={{ width: "75%", height: "300px" }}>
<DxcImage
alt="Spaceship pic"
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="
objectFit="cover"
width="50%"
height="100%"
objectPosition="0px 0px"
/>
<DxcImage
alt="Spaceship pic"
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="
objectFit="cover"
width="50%"
height="100%"
objectPosition="0px 0px"
/>
</div>
</ExampleContainer>
</>
);
69 changes: 69 additions & 0 deletions lib/src/image/Image.tsx
@@ -0,0 +1,69 @@
import React from "react";
import ImagePropsType, { CaptionWrapperProps } from "./types";
import styled, { ThemeProvider } from "styled-components";
import useTheme from "../useTheme";
import BaseTypography from "../utils/BaseTypography";

const CaptionWrapper = ({ condition, wrapper, children }: CaptionWrapperProps): JSX.Element => (
<>{condition ? wrapper(children) : children}</>
);

const DxcImage = ({
alt,
caption,
lazyLoading = false,
src,
srcSet,
sizes,
width,
height,
objectFit,
objectPosition,
onLoad,
onError,
}: ImagePropsType) => {
const colorsTheme = useTheme();

return (
<ThemeProvider theme={colorsTheme.image}>
<CaptionWrapper
condition={caption != undefined}
wrapper={(children: React.ReactNode) => (
<Figure>
{children}
<BaseTypography as="figcaption" color={colorsTheme.image.captionFontColor} fontSize="0.875rem">
{caption}
</BaseTypography>
</Figure>
)}
>
<img
alt={alt}
loading={lazyLoading ? "lazy" : undefined}
onLoad={onLoad}
onError={onError}
src={src}
srcSet={srcSet}
sizes={sizes}
style={{
objectFit,
objectPosition,
width,
height,
}}
/>
</CaptionWrapper>
</ThemeProvider>
);
};

const Figure = styled.figure`
display: flex;
flex-direction: column;
gap: 1rem;
width: fit-content;
margin: 0;
padding: 0;
`;

export default DxcImage;
73 changes: 73 additions & 0 deletions lib/src/image/types.ts
@@ -0,0 +1,73 @@
type Props = {
/**
* Alternative text description displayed when the specified image is not loaded.
*
* See MDN: https://developer.mozilla.org/en-US/docs/Web/API/HTMLImageElement/alt
* See W3C alt decision tree: https://www.w3.org/WAI/tutorials/images/decision-tree/
*/
alt: string;
/**
* Image legend with a descriptive purpose. It is placed below the image and is complementary to the alt attribute,
* which is required regardless of the presence of the caption or not.
*/
caption?: string;
raquelarrojo marked this conversation as resolved.
Show resolved Hide resolved
/**
* If true, the image will be loaded only when it is visible on the screen (lazy loading).
* Otherwise and by default, the image will be loaded as soon as the component is mounted (eager loading).
*/
lazyLoading?: boolean;
/**
* URL of the image. This prop is required and must be valid.
*/
src: string;
/**
* List of one or more strings separated by commas indicating a set of possible images for the user agent to use.
*
* See MDN: https://developer.mozilla.org/en-US/docs/Web/API/HTMLImageElement/srcset
*/
srcSet?: string;
/**
* One or more strings separated by commas, indicating a set of source sizes.
* If the srcSet attribute is absent or contains no values with a width descriptor,
* then this attribute has no effect.
*
* See MDN: https://developer.mozilla.org/en-US/docs/Web/API/HTMLImageElement/sizes
*/
sizes?: string;
/**
* Sets the rendered width of the image.
*/
width?: string;
/**
* Sets the rendered height of the image.
*/
height?: string;
/**
* Sets the object-fit CSS property.
*
* See MDN: https://developer.mozilla.org/en-US/docs/Web/CSS/object-fit
*/
objectFit?: "contain" | "cover" | "fill" | "none" | "scale-down";
/**
* Sets the object-position CSS property.
*
* See MDN: https://developer.mozilla.org/en-US/docs/Web/CSS/object-position
*/
objectPosition?: string;
/**
* This function will be called when the image is loaded.
*/
onLoad?: React.ReactEventHandler<HTMLImageElement>;
/**
* This function will be called when the image fails to load.
*/
onError?: React.ReactEventHandler<HTMLImageElement>;
};

export type CaptionWrapperProps = {
condition: boolean;
wrapper: (children: React.ReactNode) => JSX.Element;
children: React.ReactNode;
};

export default Props;
2 changes: 2 additions & 0 deletions lib/src/main.ts
Expand Up @@ -40,6 +40,7 @@ import DxcTypography from "./typography/Typography";
import DxcParagraph from "./paragraph/Paragraph";
import DxcBulletedList from "./bulleted-list/BulletedList";
import DxcGrid from "./grid/Grid";
import DxcImage from "./image/Image";

import HalstackContext, { HalstackProvider, HalstackLanguageContext } from "./HalstackContext";
import { BackgroundColorProvider } from "./BackgroundColorContext";
Expand Down Expand Up @@ -91,4 +92,5 @@ export {
DxcParagraph,
DxcBulletedList,
DxcGrid,
DxcImage,
};
2 changes: 1 addition & 1 deletion website/pages/components/flex/usage.tsx
Expand Up @@ -7,7 +7,7 @@ const Usage = () => {
return (
<>
<Head>
<title>Flex Usage— Halstack Design System</title>
<title>Flex Usage — Halstack Design System</title>
</Head>
<FlexUsagePage></FlexUsagePage>
</>
Expand Down
21 changes: 21 additions & 0 deletions website/pages/components/image/index.tsx
@@ -0,0 +1,21 @@
import Head from "next/head";
import type { ReactElement } from "react";
import ImagePageLayout from "../../../screens/components/image/ImagePageLayout";
import ImageCodePage from "../../../screens/components/image/code/ImageCodePage";

const Index = () => {
return (
<>
<Head>
<title>Image — Halstack Design System</title>
</Head>
<ImageCodePage></ImageCodePage>
</>
);
};

Index.getLayout = function getLayout(page: ReactElement) {
return <ImagePageLayout>{page}</ImagePageLayout>;
};

export default Index;
1 change: 1 addition & 0 deletions website/screens/common/componentList.js
Expand Up @@ -26,6 +26,7 @@ exports.componentsList = [
{ label: "Grid", path: "/components/grid", status: "Experimental" },
{ label: "Header", path: "/components/header", status: "Ready" },
{ label: "Heading", path: "/components/heading", status: "Ready" },
{ label: "Image", path: "/components/image", status: "Experimental" },
{ label: "Inset", path: "/components/inset", status: "Ready" },
{ label: "Link", path: "/components/link", status: "Ready" },
{ label: "Nav Tabs", path: "/components/nav-tabs", status: "Ready" },
Expand Down
26 changes: 26 additions & 0 deletions website/screens/components/image/ImagePageLayout.tsx
@@ -0,0 +1,26 @@
import { DxcParagraph, DxcFlex } from "@dxc-technology/halstack-react";
import PageHeading from "@/common/PageHeading";
import TabsPageHeading from "@/common/TabsPageLayout";
import ComponentHeading from "@/common/ComponentHeading";

const ImagePageHeading = ({ children }: { children: React.ReactNode }) => {
const tabs = [{ label: "Code", path: "/components/image" }];

return (
<DxcFlex direction="column" gap="3rem">
<PageHeading>
<DxcFlex direction="column" gap="2rem">
<ComponentHeading name="Image" />
<DxcParagraph>
The Image component is used to embed images in Halstack-based user
interfaces.
</DxcParagraph>
<TabsPageHeading tabs={tabs}></TabsPageHeading>
</DxcFlex>
</PageHeading>
{children}
</DxcFlex>
);
};

export default ImagePageHeading;