Skip to content

Commit

Permalink
Adds Image web component (#26936)
Browse files Browse the repository at this point in the history
* Adds Image web component.

* Removes unused portions of the doc file (now spec.md). Renames and simplifies the bordered attribute. Removes multiple image from first story. Adjusts style attribute selctor for bordered.

* Removes unused CSS. Changes dimension of image used in first story.

* Removes borderRadius attribute and related story code and styles. Adds CSS Guidance to spec to convey details about margin and border-radius values. Cleaned up story code.

* Records results of yarn change

* Updates package with reference to the component. Makes fit and shape optional. Replaces hard coded border radius value with token.

* Update change/@fluentui-web-components-a862230b-9028-4c6a-8a43-3be819a6128e.json

Co-authored-by: Miroslav Stastny <mistastn@microsoft.com>

---------

Co-authored-by: Chris Holt <chhol@microsoft.com>
Co-authored-by: Miroslav Stastny <mistastn@microsoft.com>
  • Loading branch information
3 people authored and radium-v committed May 2, 2024
1 parent d0f22af commit 39bf1ee
Show file tree
Hide file tree
Showing 12 changed files with 474 additions and 0 deletions.
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
{
"type": "prerelease",
"comment": "feat(image): Add image web component",
"packageName": "@fluentui/web-components",
"email": "harankin@microsoft.com",
"dependentChangeType": "patch"
}
4 changes: 4 additions & 0 deletions packages/web-components/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,10 @@
"types": "./dist/esm/counter-badge/define.d.ts",
"default": "./dist/esm/counter-badge/define.js"
},
"./image": {
"types": "./dist/esm/image/define.d.ts",
"default": "./dist/esm/image/define.js"
},
"./text": {
"types": "./dist/esm/text/define.d.ts",
"default": "./dist/esm/text/define.js"
Expand Down
4 changes: 4 additions & 0 deletions packages/web-components/src/image/define.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
import { FluentDesignSystem } from '../fluent-design-system.js';
import { definition } from './image.definition.js';

definition.define(FluentDesignSystem.registry);
17 changes: 17 additions & 0 deletions packages/web-components/src/image/image.definition.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
import { FluentDesignSystem } from '../fluent-design-system.js';
import { Image } from './image.js';
import { template } from './image.template.js';
import { styles } from './image.styles.js';

/**
* The Fluent Image Element
*
* @public
* @remarks
* HTML Element: \<fluent-image\>
*/
export const definition = Image.compose({
name: `${FluentDesignSystem.prefix}-image`,
template,
styles,
});
30 changes: 30 additions & 0 deletions packages/web-components/src/image/image.options.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
import { ValuesOf } from '@microsoft/fast-foundation';

/**
* Image fit
* @public
*/
export const ImageFit = {
none: 'none',
center: 'center',
contain: 'contain',
cover: 'cover',
default: 'default',
} as const;
/**
* Types for image fit
* @public
*/
export type ImageFit = ValuesOf<typeof ImageFit>;

/**
* Image shape
* @public
*/
export const ImageShape = {
circular: 'circular',
rounded: 'rounded',
square: 'square',
} as const;

export type ImageShape = ValuesOf<typeof ImageShape>;
62 changes: 62 additions & 0 deletions packages/web-components/src/image/image.spec.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,62 @@
# Fluent Image Component

## Component Description

Images, like photos and illustrations, help reinforce a message and express your product or app’s style.

## Design Spec

[Image Spec in Figma](https://www.figma.com/file/05wt6TAsEmgsCVZfPrpcWx/Image?t=uEvu1KnTefdTZHJC-6)

## Engineering Spec

### Inputs

**content**

- @attr public alt: string | Requires description if image role is not set to presentation.
- @attr public role: string
- @attr public src: string

**booleans**

- @attr public block: boolean | false
- @attr public border: boolean | false
- @attr public shadow: boolean | false

**options**

- @attr public fit: 'none' | 'center' | 'contain' | 'cover' | 'default'
- @attr public shape: 'square' | 'rounded' | 'circular'

### Slots

1 slot for developer to add <img/> element.

## Accessibility

The image element requires an alt tag when not used in role: presentation.

## Preparation

This will extend the FASTElement.

Open GitHub issues related to Image component

- [Feature request](https://github.com/microsoft/fluentui/issues/26452)
- [Bug](https://github.com/microsoft/fluentui/issues/26399)

## Implementation

### CSS Guidance

- [x] Uses design tokens for styling

An optional border-radius can be expressed using the following design tokens:

- borderRadiusSmall,
- borderRadiusMedium,
- borderRadiusLarge
- borderRadiusXLarge

An optional 16px margin can be added to the image to separate it from surrounding content.
230 changes: 230 additions & 0 deletions packages/web-components/src/image/image.stories.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,230 @@
import { html } from '@microsoft/fast-element';
import type { Args, Meta } from '@storybook/html';
import { renderComponent } from '../helpers.stories.js';
import type { Image as FluentImage } from './image.js';
import { ImageFit, ImageShape } from './image.options.js';
import './define.js';

type ImageStoryArgs = Args & FluentImage;
type ImageStoryMeta = Meta<ImageStoryArgs>;

const imageTemplate = html<ImageStoryArgs>`
<div style="padding: 48px 24px; background-color: rgb(250, 250, 250);">
<fluent-image
?block=${x => x.block}
?bordered=${x => x.bordered}
fit=${x => x.fit}
?shadow=${x => x.shadow}
shape=${x => x.shape}
>
<img alt="Short image description" src="https://via.placeholder.com/300x100/ddd.png" />
</fluent-image>
</div>
`;

export default {
title: 'Components/Image',
args: {
block: false,
bordered: false,
shadow: false,
fit: ImageFit.default,
shape: ImageShape.square,
},
argTypes: {
alt: {
description: 'Alternate text description -- to be supplied by component consumer',
table: {
type: {
summary:
'Required. Alt tag provides text attribution for images. Should be brief but accurate—one or two sentences that describe the image and its context. If the image represents a function, be sure to indicate that. If it’s meant to be consumed with other objects on the page, consider that as well. Don’t repeat information that’s on the page in alt text since screen readers will read it twice.',
},
},
},
block: {
description:
'An image can use the argument ‘block’ so that it’s width will expand to fiill the available container space.',
table: {
defaultValue: {
summary: false,
},
},
},
bordered: {
description: 'Border surrounding image',
table: {
type: {
summary: 'Use this option to provide minimal visual separation between image and surrounding content.',
},
defaultValue: {
summary: false,
},
},
},
fit: {
description: 'Determines how the image will be scaled and positioned within its parent container.',
table: {
defaultValue: {
summary: 'default',
},
},
options: Object.values(ImageFit),
control: 'select',
},
role: {
description: 'Aria role -- to be supplied by component consumer',
table: {
type: {
summary:
'If images are solely decorative and don’t provide useful information or context, use role=”presentation” to hide them from assistive technologies.',
},
},
},
shadow: {
description: 'Apply an optional box shadow to further separate the image from the background.',
table: {
type: {
summary:
'To give an image additional prominence, use the shadow prop to make it appear elevated. Too many shadows can cause a busy layout, so use them sparingly.',
},
defaultValue: {
summary: false,
},
},
},
shape: {
description: 'Image shape',
table: {
defaultValue: {
summary: 'square',
},
},
options: Object.values(ImageShape),
control: 'select',
},
src: {
description: 'Image source -- to be supplied by component consumer',
table: {
type: {
summary: 'Required',
},
},
},
},
} as ImageStoryMeta;

export const Image = renderComponent(imageTemplate).bind({});

// Block layout
const imageLayoutBlock = html<ImageStoryArgs>`
<div style="border: 1px dotted #43ED35;">
<fluent-image block bordered>
<img role="presentation" src="https://via.placeholder.com/958x20/ddd.png" />
<img role="presentation" src="https://via.placeholder.com/100x100/ddd.png" />
</fluent-image>
</div>
`;
export const BlockLayout = renderComponent(imageLayoutBlock).bind({});

// Fit: None
const imageFitNoneLarge = html<ImageStoryArgs>`
<div style="height: 150px; width: 300px; border: 1px dotted #43ED35;">
<fluent-image bordered fit="none">
<img role="presentation" src="https://via.placeholder.com/600x200/ddd.png" />
</fluent-image>
</div>
`;
export const ImageFitNoneLarge = renderComponent(imageFitNoneLarge).bind({});

const imageFitNoneSmall = html<ImageStoryArgs>`
<div style="height: 150px; width: 300px; border: 1px dotted #43ED35;">
<fluent-image bordered fit="none">
<img alt="200x100 placeholder" src="https://via.placeholder.com/200x100/ddd.png" />
</fluent-image>
</div>
`;
export const ImageFitNoneSmall = renderComponent(imageFitNoneSmall).bind({});

// Fit: Center
const imageFitCenterLarge = html<ImageStoryArgs>`
<div style="height: 210px; width: 650px; border: 1px dotted #43ED35;">
<fluent-image bordered fit="center">
<img role="presentation" src="https://via.placeholder.com/600x200/ddd.png" />
</fluent-image>
</div>
`;
export const ImageFitCenterLarge = renderComponent(imageFitCenterLarge).bind({});

const imageFitCenterSmall = html<ImageStoryArgs>`
<div style="height: 210px; width: 650px; border: 1px dotted #43ED35;">
<fluent-image bordered fit="center">
<img alt="image layout story" src="https://via.placeholder.com/200x100/ddd.png" />
</fluent-image>
</div>
`;
export const ImageFitCenterSmall = renderComponent(imageFitCenterSmall).bind({});

const imageFitContain = html<ImageStoryArgs>`
<div style="height: 200px; width: 400px; border: 1px dotted #43ED35;">
<fluent-image bordered fit="contain">
<img alt="image layout story" src="https://via.placeholder.com/400x200/ddd.png" />
</fluent-image>
</div>
`;
export const ImageFitContain = renderComponent(imageFitContain).bind({});

const imageFitContainTall = html<ImageStoryArgs>`
<div style="height: 250px; width: 400px; border: 1px dotted #43ED35;">
<fluent-image bordered fit="contain">
<img alt="image layout story" src="https://via.placeholder.com/400x200/ddd.png" />
</fluent-image>
</div>
`;
export const ImageFitContainTall = renderComponent(imageFitContainTall).bind({});

const imageFitContainWide = html<ImageStoryArgs>`
<div style="height: 200px; width: 450px; border: 1px dotted #43ED35;">
<fluent-image bordered fit="contain">
<img alt="image layout story" src="https://via.placeholder.com/400x200/ddd.png" />
</fluent-image>
</div>
`;
export const ImageFitContainWide = renderComponent(imageFitContainWide).bind({});

// Fit: Cover
const imageFitCoverSmall = html<ImageStoryArgs>`
<div style="height: 200px; width: 400px; border: 1px dotted #43ED35;">
<fluent-image bordered fit="cover">
<img alt="image layout story" src="https://via.placeholder.com/400x250/ddd.png" />
</fluent-image>
</div>
`;
export const ImageFitCoverSmall = renderComponent(imageFitCoverSmall).bind({});

const imageFitCoverMedium = html<ImageStoryArgs>`
<div style="height: 200px; width: 400px; border: 1px dotted #43ED35;">
<fluent-image bordered fit="cover">
<img alt="image layout story" src="https://via.placeholder.com/400x300/ddd.png" />
</fluent-image>
</div>
`;
export const ImageFitCoverMedium = renderComponent(imageFitCoverMedium).bind({});

const imageFitCoverLarge = html<ImageStoryArgs>`
<div style="height: 200px; width: 400px; border: 1px dotted #43ED35;">
<fluent-image bordered fit="cover">
<img alt="image layout story" src="https://via.placeholder.com/600x200/ddd.png" />
</fluent-image>
</div>
`;
export const ImageFitCoverLarge = renderComponent(imageFitCoverLarge).bind({});

// Fit: Default
const imageFitDefault = html<ImageStoryArgs>`
<div style="height: 210px; width: 650px; border: 1px dotted #43ED35;">
<fluent-image bordered fit="default">
<img alt="image layout story" src="https://via.placeholder.com/150/ddd.png" />
</fluent-image>
</div>
`;
export const ImageFitDefault = renderComponent(imageFitDefault).bind({});
Loading

0 comments on commit 39bf1ee

Please sign in to comment.