From efd28523bc46f1ca14f308fbb3e39b5b967269d7 Mon Sep 17 00:00:00 2001 From: Elizabet Oliveira Date: Wed, 15 Jun 2022 15:58:53 +0100 Subject: [PATCH 01/48] Initial conversion --- src/components/image/_index.scss | 2 +- src/components/image/image.styles.ts | 253 +++++++++++++++++++++ src/components/image/image.tsx | 173 +++++++------- src/components/index.scss | 1 - src/themes/amsterdam/overrides/_image.scss | 10 - src/themes/amsterdam/overrides/_index.scss | 1 - 6 files changed, 348 insertions(+), 92 deletions(-) create mode 100644 src/components/image/image.styles.ts delete mode 100644 src/themes/amsterdam/overrides/_image.scss diff --git a/src/components/image/_index.scss b/src/components/image/_index.scss index eb326aae4dd..e36d0f9459e 100644 --- a/src/components/image/_index.scss +++ b/src/components/image/_index.scss @@ -1 +1 @@ -@import 'image'; +// @import 'image'; diff --git a/src/components/image/image.styles.ts b/src/components/image/image.styles.ts new file mode 100644 index 00000000000..b98d10ed881 --- /dev/null +++ b/src/components/image/image.styles.ts @@ -0,0 +1,253 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0 and the Server Side Public License, v 1; you may not use this file except + * in compliance with, at your election, the Elastic License 2.0 or the Server + * Side Public License, v 1. + */ + +import { css, keyframes } from '@emotion/react'; +import { + euiFocusRing, + euiFontSize, + euiCanAnimate, + logicalCSS, +} from '../../global_styling'; +import { + UseEuiTheme, + isWithinBreakpoints, + transparentize, +} from '../../services'; +import { euiShadow } from '../../themes/amsterdam/global_styling/mixins'; +import type { EuiImageSize } from './image'; + +const _convertToRem = (size: number) => { + return `${size / 16}rem`; +}; + +export const euiImageStyles = ( + euiThemeContext: UseEuiTheme, + size: EuiImageSize | number | string +) => { + const { euiTheme } = euiThemeContext; + + return { + // Base + euiImage: css` + display: inline-block; + max-width: 100%; + position: relative; + min-height: 1px; /* 1 */ + line-height: 0; // Fixes cropping when image is resized by forcing its height to be determined by the image not line-height + flex-shrink: 0; // Don't ever let this shrink in height if direct descendent of flex + + ${isWithinBreakpoints(window.innerWidth, ['xs', 's', 'm'])} { + &[class*='-left'], + &[class*='-right'] { + float: none; + // Return back to whatever margin settings were set without the float + margin-top: inherit; + margin-right: inherit; + margin-bottom: inherit; + margin-left: inherit; + } + } + `, + customSizeStyle: css` + max-width: ${typeof size === 'string' ? size : `${size}px`}; + max-height: ${typeof size === 'string' ? size : `${size}px`}; + // Set width back to auto to ensure aspect ratio is kept + width: auto; + `, + allowFullScreen: css` + &:hover .euiImage__caption { + text-decoration: underline; + } + + &:not([class*='-hasShadow']) [class*='euiImage__button']:hover, + &:not([class*='-hasShadow']) [class*='euiImage__button']:focus { + ${euiShadow(euiThemeContext, 'm')}; + } + + &.euiImage--hasShadow .euiImage__button:hover, + &.euiImage--hasShadow .euiImage__button:focus { + ${euiShadow(euiThemeContext, 's')}; + } + `, + isFullScreenActive: css` + position: relative; + max-height: 80vh; + max-width: 80vw; + ${euiCanAnimate} { + animation: ${euiImageFullScreen(euiTheme.size.xxxxl)} + ${euiTheme.animation.extraSlow} ${euiTheme.animation.bounce}; + } + + [class*='euiImage__caption'] { + color: ${euiTheme.colors.ghost}; + text-shadow: 0 1px 2px ${transparentize(euiTheme.colors.ink, 0.6)}; + } + + &:hover { + [class*='euiImage__button'] { + ${euiShadow(euiThemeContext, 's')}; + } + + [class*='euiImage__caption'] { + text-decoration: underline; + } + } + `, + // margins + s: css` + margin: ${euiTheme.size.s}; + `, + m: css` + margin: ${euiTheme.size.base}; + `, + l: css` + margin: ${euiTheme.size.l}; + `, + xl: css` + margin: ${euiTheme.size.xl}; + `, + // floats + left: css` + float: left; + margin-left: 0 !important; + margin-top: 0 !important; + `, + right: css` + float: right; + margin-right: 0 !important; + margin-top: 0 !important; + `, + }; +}; + +export const euiImageImgStyles = (euiThemeContext: UseEuiTheme) => { + return { + // The image itself is full width within the container. + euiImage__img: css` + width: 100%; + vertical-align: middle; + // Required for common usage of nesting within EuiText + margin-bottom: 0 !important; + max-width: 100%; + `, + hasShadow: css` + ${euiShadow(euiThemeContext, 's')}; + `, + // Sizes + // These sizes are mostly suggestions. Don't look too hard for meaning in their values. + // Size is applied to the image, rather than the figure to work better with floats + s: css` + width: ${_convertToRem(120)}; + `, + m: css` + width: ${_convertToRem(200)}; + `, + l: css` + width: ${_convertToRem(360)}; + `, + xl: css` + width: ${_convertToRem(600)}; + `, + fullWidth: css` + width: 100%; + `, + original: css` + width: auto; + max-width: 100%; + `, + }; +}; + +export const euiImageButtonStyles = ({ euiTheme }: UseEuiTheme) => ({ + // Base + euiImage__button: css` + position: relative; + cursor: pointer; + + // transition the shadow + transition: all ${euiTheme.animation.fast} ${euiTheme.animation.resistance}; + + &:focus { + ${euiFocusRing(euiTheme, 'outset')} + } + + &:hover [class*='euiImage__icon'] { + visibility: visible; + fill-opacity: 1; + } + `, + fullWidth: css` + width: 100%; + `, +}); + +export const euiImageCaptionStyles = (euiThemeContext: UseEuiTheme) => { + const { euiTheme } = euiThemeContext; + + return { + // Base + euiImage__caption: css` + ${euiFontSize(euiThemeContext, 's')}; + ${logicalCSS('margin-top', euiTheme.size.xs)}; + text-align: center; + `, + }; +}; + +export const euiImageIconStyles = ({ euiTheme }: UseEuiTheme) => ({ + // Base + euiImage__icon: css` + visibility: hidden; + fill-opacity: 0; + position: absolute; + ${logicalCSS('top', euiTheme.size.base)}; + ${logicalCSS('right', euiTheme.size.base)}; + cursor: pointer; + transition: fill-opacity ${euiTheme.animation.slow} + ${euiTheme.animation.resistance}; + `, +}); + +export const euiImageFullScreenImgStyles = ({ euiTheme }: UseEuiTheme) => ({ + // Base + euiImage__fullScreenImg: css` + position: relative; + max-height: 80vh; + max-width: 80vw; + + ${euiCanAnimate} { + animation: ${euiImageFullScreen(euiTheme.size.xxxxl)} + ${euiTheme.animation.extraSlow} ${euiTheme.animation.bounce}; + } + `, +}); + +export const euiImageFullScreenCloseIconStyles = ({ + euiTheme, +}: UseEuiTheme) => ({ + // Base + euiImage__fullScreenCloseIcon: css` + position: absolute; + ${logicalCSS('top', euiTheme.size.base)}; + ${logicalCSS('right', euiTheme.size.base)}; + pointer-events: none; + fill: ${euiTheme.colors.ghost}; + `, +}); + +const euiImageFullScreen = (size: string) => keyframes` + 0% { + opacity: 0; + transform: translateY(${size}); + } + + 100% { + opacity: 1; + transform: translateY(0); + } +`; diff --git a/src/components/image/image.tsx b/src/components/image/image.tsx index 345b0c732a0..b950a23ca5d 100644 --- a/src/components/image/image.tsx +++ b/src/components/image/image.tsx @@ -23,35 +23,26 @@ import { useEuiI18n } from '../i18n'; import { EuiFocusTrap } from '../focus_trap'; -import { keys } from '../../services'; +import { keys, useEuiTheme } from '../../services'; import { useInnerText } from '../inner_text'; +import { + euiImageStyles, + euiImageImgStyles, + euiImageButtonStyles, + euiImageCaptionStyles, + euiImageIconStyles, + euiImageFullScreenImgStyles, + euiImageFullScreenCloseIconStyles, +} from './image.styles'; -type ImageSize = 's' | 'm' | 'l' | 'xl' | 'fullWidth' | 'original'; -type Floats = 'left' | 'right'; -type Margins = 's' | 'm' | 'l' | 'xl'; - -const sizeToClassNameMap: { [size in ImageSize]: string } = { - s: 'euiImage--small', - m: 'euiImage--medium', - l: 'euiImage--large', - xl: 'euiImage--xlarge', - fullWidth: 'euiImage--fullWidth', - original: 'euiImage--original', -}; +export const SIZES = ['s', 'm', 'l', 'xl', 'fullWidth', 'original'] as const; +export type EuiImageSize = typeof SIZES[number]; -const marginToClassNameMap: { [margin in Margins]: string } = { - s: 'euiImage--marginSmall', - m: 'euiImage--marginMedium', - l: 'euiImage--marginLarge', - xl: 'euiImage--marginXlarge', -}; +const FLOATS = ['left', 'right'] as const; +export type EuiImageFloat = typeof FLOATS[number]; -const floatToClassNameMap: { [float in Floats]: string } = { - left: 'euiImage--floatLeft', - right: 'euiImage--floatRight', -}; - -export const SIZES = Object.keys(sizeToClassNameMap); +const MARGINS = ['s', 'm', 'l', 'xl'] as const; +export type EuiImageMargin = typeof MARGINS[number]; type FullScreenIconColor = 'light' | 'dark'; @@ -85,7 +76,7 @@ export type EuiImageProps = CommonProps & * `fullWidth` will set the figure to stretch to 100% of its container. * `string` and `number` types will max both the width or height, whichever is greater. */ - size?: ImageSize | number | string; + size?: EuiImageSize | number | string; /** * Changes the color of the icon that floats above the image when it can be clicked to fullscreen. * The default value of `light` is fine unless your image has a white background, in which case you should change it to `dark`. @@ -106,11 +97,11 @@ export type EuiImageProps = CommonProps & /** * Float the image to the left or right. Useful in large text blocks. */ - float?: Floats; + float?: EuiImageFloat; /** * Margin around the image. */ - margin?: Margins; + margin?: EuiImageMargin; }; export const EuiImage: FunctionComponent = ({ @@ -148,42 +139,63 @@ export const EuiImage: FunctionComponent = ({ const customStyle: React.CSSProperties = { ...style }; - let classes = classNames( - 'euiImage', - { - 'euiImage--hasShadow': hasShadow, - 'euiImage--allowFullScreen': allowFullScreen, - }, - margin ? marginToClassNameMap[margin] : null, - float ? floatToClassNameMap[float] : null, - className - ); + const classes = classNames('euiImage', className); - if (typeof size === 'string' && SIZES.includes(size)) { - classes = `${classes} ${sizeToClassNameMap[size as ImageSize]}`; - } else { - classes = `${classes}`; - customStyle.maxWidth = size; - customStyle.maxHeight = size; - // Set width back to auto to ensure aspect ratio is kept - customStyle.width = 'auto'; - } + const euiTheme = useEuiTheme(); - let allowFullScreenButtonClasses = 'euiImage__button'; + const styles = euiImageStyles(euiTheme, size); + const cssStyles = [ + styles.euiImage, + float && styles[float], + margin && styles[margin], + allowFullScreen && styles.allowFullScreen, + isFullScreenActive && styles.isFullScreenActive, + ]; - // when the button is not custom we need it to go full width - // to match the parent '.euiImage' width except when the size is original - if (typeof size === 'string' && size !== 'original' && SIZES.includes(size)) { - allowFullScreenButtonClasses = `${allowFullScreenButtonClasses} euiImage__button--fullWidth`; + const imgStyles = euiImageImgStyles(euiTheme); + let cssImgStyles; + + if (typeof size === 'string' && SIZES.includes(size as EuiImageSize)) { + cssImgStyles = [ + imgStyles.euiImage__img, + imgStyles[size as EuiImageSize], + hasShadow && imgStyles.hasShadow, + ]; } else { - allowFullScreenButtonClasses = `${allowFullScreenButtonClasses}`; + cssImgStyles = [imgStyles.euiImage__img, hasShadow && imgStyles.hasShadow]; + cssStyles.push(styles.customSizeStyle); } + const buttonStyles = euiImageButtonStyles(euiTheme); + const cssButtonStyles = [ + buttonStyles.euiImage__button, + // when the button is not custom we need it to go full width + // to match the parent '.euiImage' width except when the size is original + typeof size === 'string' && + size !== 'original' && + SIZES.includes(size as EuiImageSize) && + buttonStyles.fullWidth, + ]; + + const captionStyles = euiImageCaptionStyles(euiTheme); + const cssCaptionStyles = [captionStyles.euiImage__caption]; + + const iconStyles = euiImageIconStyles(euiTheme); + const cssIconStyles = [iconStyles.euiImage__icon]; + + const fullScreenImgStyles = euiImageFullScreenImgStyles(euiTheme); + const cssFullScreenImgStyles = [fullScreenImgStyles.euiImage__fullScreenImg]; + + const fullScreenCloseIconStyles = euiImageFullScreenCloseIconStyles(euiTheme); + const cssFullScreenCloseIconStyles = [ + fullScreenCloseIconStyles.euiImage__fullScreenCloseIcon, + ]; + const [optionalCaptionRef, optionalCaptionText] = useInnerText(); let optionalCaption; if (caption) { optionalCaption = ( -
+
{caption}
); @@ -193,7 +205,7 @@ export const EuiImage: FunctionComponent = ({ ); @@ -204,18 +216,15 @@ export const EuiImage: FunctionComponent = ({ > <> -
+
@@ -232,7 +241,7 @@ export const EuiImage: FunctionComponent = ({ @@ -245,23 +254,31 @@ export const EuiImage: FunctionComponent = ({ { alt } ); + const img = ( + {alt} + ); + if (allowFullScreen) { return ( -
+
{isFullScreenActive && fullScreenDisplay} @@ -270,14 +287,12 @@ export const EuiImage: FunctionComponent = ({ ); } else { return ( -
- {alt} +
+ {img} {optionalCaption}
); diff --git a/src/components/index.scss b/src/components/index.scss index 6123a843ab7..ed824eea783 100644 --- a/src/components/index.scss +++ b/src/components/index.scss @@ -27,7 +27,6 @@ @import 'form/index'; @import 'header/index'; @import 'icon/index'; -@import 'image/index'; @import 'key_pad_menu/index'; @import 'list_group/index'; @import 'markdown_editor/index'; diff --git a/src/themes/amsterdam/overrides/_image.scss b/src/themes/amsterdam/overrides/_image.scss deleted file mode 100644 index 3e240097340..00000000000 --- a/src/themes/amsterdam/overrides/_image.scss +++ /dev/null @@ -1,10 +0,0 @@ -.euiImage-isFullScreen { - .euiImage__caption { - color: $euiColorGhost; - text-shadow: 0 1px 2px transparentize($euiColorInk, .6); - } -} - -.euiImage-isFullScreenCloseIcon { - fill: $euiColorGhost; -} \ No newline at end of file diff --git a/src/themes/amsterdam/overrides/_index.scss b/src/themes/amsterdam/overrides/_index.scss index 830da42b470..6f6980450b0 100644 --- a/src/themes/amsterdam/overrides/_index.scss +++ b/src/themes/amsterdam/overrides/_index.scss @@ -19,7 +19,6 @@ @import 'header'; @import 'hue'; @import 'list_group_item'; -@import 'image'; @import 'key_pad_menu'; @import 'markdown_editor'; @import 'modal'; From 3a1244d24d3ee1ed6c3d3cf0f7aff6c1c04f51d3 Mon Sep 17 00:00:00 2001 From: Elizabet Oliveira Date: Wed, 15 Jun 2022 16:03:00 +0100 Subject: [PATCH 02/48] Fixing close button color --- src/components/image/image.styles.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/components/image/image.styles.ts b/src/components/image/image.styles.ts index b98d10ed881..b23806995fc 100644 --- a/src/components/image/image.styles.ts +++ b/src/components/image/image.styles.ts @@ -236,7 +236,7 @@ export const euiImageFullScreenCloseIconStyles = ({ ${logicalCSS('top', euiTheme.size.base)}; ${logicalCSS('right', euiTheme.size.base)}; pointer-events: none; - fill: ${euiTheme.colors.ghost}; + fill: ${euiTheme.colors.ghost} !important; `, }); From f558663efb99e13462e7a6e65d3f5f622428e353 Mon Sep 17 00:00:00 2001 From: Elizabet Oliveira Date: Thu, 16 Jun 2022 17:07:20 +0100 Subject: [PATCH 03/48] Removing unused sass files --- src/components/image/_image.scss | 212 ------------------------------- src/components/image/_index.scss | 1 - 2 files changed, 213 deletions(-) delete mode 100644 src/components/image/_image.scss delete mode 100644 src/components/image/_index.scss diff --git a/src/components/image/_image.scss b/src/components/image/_image.scss deleted file mode 100644 index 94489b5397b..00000000000 --- a/src/components/image/_image.scss +++ /dev/null @@ -1,212 +0,0 @@ -/** - * 1. Fix for IE where the image correctly resizes in width but doesn't collapse its height - (https://github.com/philipwalton/flexbugs/issues/75#issuecomment-134702421) - */ - -// Main
that wraps images. -.euiImage { - display: inline-block; - max-width: 100%; - position: relative; - min-height: 1px; /* 1 */ - line-height: 0; // Fixes cropping when image is resized by forcing its height to be determined by the image not line-height - flex-shrink: 0; // Don't ever let this shrink in height if direct descendent of flex - - // Required for common usage of nesting within EuiText - .euiImage__img { - margin-bottom: 0; - max-width: 100%; - } - - &.euiImage--hasShadow { - .euiImage__img { - @include euiBottomShadowMedium; - } - } - - .euiImage__button { - position: relative; - cursor: pointer; - - // transition the shadow - transition: all $euiAnimSpeedFast $euiAnimSlightResistance; - - &:focus { - outline: 2px solid $euiFocusRingColor; - } - - &:hover .euiImage__icon { - visibility: visible; - fill-opacity: 1; - } - - &--fullWidth { - width: 100%; - } - } - - &.euiImage--allowFullScreen { - &:hover .euiImage__caption { - text-decoration: underline; - } - - &:not(.euiImage--hasShadow) .euiImage__button:hover, - &:not(.euiImage--hasShadow) .euiImage__button:focus { - @include euiBottomShadowMedium; - } - - &.euiImage--hasShadow .euiImage__button:hover, - &.euiImage--hasShadow .euiImage__button:focus { - @include euiBottomShadow; - } - } - - // These sizes are mostly suggestions. Don't look too hard for meaning in their values. - // Size is applied to the image, rather than the figure to work better with floats - &.euiImage--small .euiImage__img { - width: convertToRem(120px); - } - - &.euiImage--medium .euiImage__img { - width: convertToRem(200px); - } - - &.euiImage--large .euiImage__img { - width: convertToRem(360px); - } - - &.euiImage--xlarge .euiImage__img { - width: convertToRem(600px); - } - - &.euiImage--fullWidth { - width: 100%; - } - - &.euiImage--original { - .euiImage__img { - width: auto; - max-width: 100%; - } - } - - &.euiImage--floatLeft { - float: left; - - &[class*='euiImage--margin'] { - margin-left: 0; - margin-top: 0; - } - } - - &.euiImage--floatRight { - float: right; - - &[class*='euiImage--margin'] { - margin-right: 0; - margin-top: 0; - } - } - - &.euiImage--marginSmall { - margin: $euiSizeS; - } - - &.euiImage--marginMedium { - margin: $euiSize; - } - - &.euiImage--marginLarge { - margin: $euiSizeL; - } - - &.euiImage--marginXlarge { - margin: $euiSizeXL; - } -} - -// The image itself is full width within the container. -.euiImage__img { - width: 100%; - vertical-align: middle; -} - -.euiImage__caption { - @include euiFontSizeS; - margin-top: $euiSizeXS; - text-align: center; -} - -.euiImage__icon { - visibility: hidden; - fill-opacity: 0; - position: absolute; - right: $euiSize; - top: $euiSize; - transition: fill-opacity $euiAnimSpeedSlow $euiAnimSlightResistance; - cursor: pointer; -} - -// The FullScreen image that optionally pops up on click. -.euiImage-isFullScreen { - position: relative; - max-height: 80vh; - max-width: 80vw; - animation: euiImageFullScreen $euiAnimSpeedExtraSlow $euiAnimSlightBounce; - - &:hover { - .euiImage__button { - @include euiBottomShadow; - } - - .euiImage__caption { - text-decoration: underline; - } - } - - &__img { - max-height: 80vh; - max-width: 80vw; - vertical-align: middle; - cursor: pointer; - transition: all $euiAnimSpeedFast $euiAnimSlightResistance; - } -} - -.euiImage-isFullScreenCloseIcon { - position: absolute; - right: $euiSize; - top: $euiSize; - pointer-events: none; -} - -@keyframes euiImageFullScreen { - 0% { - opacity: 0; - transform: translateY($euiSizeXL * 2); - } - - 100% { - opacity: 1; - transform: translateY(0); - } -} - -@include euiBreakpoint('xs', 's', 'm') { - - .euiImage { - - &.euiImage--floatLeft, - &.euiImage--floatRight { - float: none; - - // Return back to whatever margin settings were set without the float - &[class*='euiImage--margin'] { - margin-top: inherit; - margin-right: inherit; - margin-bottom: inherit; - margin-left: inherit; - } - } - } -} diff --git a/src/components/image/_index.scss b/src/components/image/_index.scss deleted file mode 100644 index e36d0f9459e..00000000000 --- a/src/components/image/_index.scss +++ /dev/null @@ -1 +0,0 @@ -// @import 'image'; From 756b90c472fd65e6ace5e4274707fc27aa52bf0c Mon Sep 17 00:00:00 2001 From: Elizabet Oliveira Date: Mon, 20 Jun 2022 13:53:37 +0100 Subject: [PATCH 04/48] Adding CL --- upcoming_changelogs/5969.md | 3 +++ 1 file changed, 3 insertions(+) create mode 100644 upcoming_changelogs/5969.md diff --git a/upcoming_changelogs/5969.md b/upcoming_changelogs/5969.md new file mode 100644 index 00000000000..c7d44001317 --- /dev/null +++ b/upcoming_changelogs/5969.md @@ -0,0 +1,3 @@ +**CSS-in-JS** + +- Converted `EuiImage` to Emotion \ No newline at end of file From 2247ac9145526a09ee71159c539bc5094b6003c3 Mon Sep 17 00:00:00 2001 From: Elizabet Oliveira Date: Tue, 21 Jun 2022 20:09:53 +0100 Subject: [PATCH 05/48] Better conditions and styles --- src/components/image/image.styles.ts | 40 +++++------ src/components/image/image.tsx | 99 ++++++++++++---------------- 2 files changed, 63 insertions(+), 76 deletions(-) diff --git a/src/components/image/image.styles.ts b/src/components/image/image.styles.ts index b23806995fc..8c0d13f6378 100644 --- a/src/components/image/image.styles.ts +++ b/src/components/image/image.styles.ts @@ -27,7 +27,7 @@ const _convertToRem = (size: number) => { export const euiImageStyles = ( euiThemeContext: UseEuiTheme, - size: EuiImageSize | number | string + hasShadow: boolean | undefined ) => { const { euiTheme } = euiThemeContext; @@ -53,17 +53,15 @@ export const euiImageStyles = ( } } `, - customSizeStyle: css` - max-width: ${typeof size === 'string' ? size : `${size}px`}; - max-height: ${typeof size === 'string' ? size : `${size}px`}; - // Set width back to auto to ensure aspect ratio is kept - width: auto; - `, allowFullScreen: css` &:hover .euiImage__caption { text-decoration: underline; } + &[class*='-fullWidth'] { + width: 100%; + } + &:not([class*='-hasShadow']) [class*='euiImage__button']:hover, &:not([class*='-hasShadow']) [class*='euiImage__button']:focus { ${euiShadow(euiThemeContext, 'm')}; @@ -74,7 +72,7 @@ export const euiImageStyles = ( ${euiShadow(euiThemeContext, 's')}; } `, - isFullScreenActive: css` + isFullScreen: css` position: relative; max-height: 80vh; max-width: 80vw; @@ -125,7 +123,10 @@ export const euiImageStyles = ( }; }; -export const euiImageImgStyles = (euiThemeContext: UseEuiTheme) => { +export const euiImageImgStyles = ( + euiThemeContext: UseEuiTheme, + size: EuiImageSize | number | string +) => { return { // The image itself is full width within the container. euiImage__img: css` @@ -153,13 +154,18 @@ export const euiImageImgStyles = (euiThemeContext: UseEuiTheme) => { xl: css` width: ${_convertToRem(600)}; `, - fullWidth: css` - width: 100%; - `, original: css` width: auto; max-width: 100%; `, + + fullWidth: css``, + customSize: css` + max-width: ${typeof size === 'string' ? size : `${size}px`}; + max-height: ${typeof size === 'string' ? size : `${size}px`}; + // Set width back to auto to ensure aspect ratio is kept + width: auto; + `, }; }; @@ -168,6 +174,7 @@ export const euiImageButtonStyles = ({ euiTheme }: UseEuiTheme) => ({ euiImage__button: css` position: relative; cursor: pointer; + line-height: 0; // transition the shadow transition: all ${euiTheme.animation.fast} ${euiTheme.animation.resistance}; @@ -213,17 +220,12 @@ export const euiImageIconStyles = ({ euiTheme }: UseEuiTheme) => ({ `, }); -export const euiImageFullScreenImgStyles = ({ euiTheme }: UseEuiTheme) => ({ +export const euiImageFullScreenImgStyles = () => ({ // Base - euiImage__fullScreenImg: css` + euiImage__fullScreen: css` position: relative; max-height: 80vh; max-width: 80vw; - - ${euiCanAnimate} { - animation: ${euiImageFullScreen(euiTheme.size.xxxxl)} - ${euiTheme.animation.extraSlow} ${euiTheme.animation.bounce}; - } `, }); diff --git a/src/components/image/image.tsx b/src/components/image/image.tsx index b950a23ca5d..99e96d8038b 100644 --- a/src/components/image/image.tsx +++ b/src/components/image/image.tsx @@ -120,6 +120,8 @@ export const EuiImage: FunctionComponent = ({ ...rest }) => { const [isFullScreenActive, setIsFullScreenActive] = useState(false); + const isNamedSize = + typeof size === 'string' && SIZES.includes(size as EuiImageSize); const onKeyDown = (event: React.KeyboardEvent) => { if (event.key === keys.ESCAPE) { @@ -137,43 +139,40 @@ export const EuiImage: FunctionComponent = ({ setIsFullScreenActive(true); }; - const customStyle: React.CSSProperties = { ...style }; - const classes = classNames('euiImage', className); const euiTheme = useEuiTheme(); - const styles = euiImageStyles(euiTheme, size); - const cssStyles = [ + const styles = euiImageStyles(euiTheme, hasShadow); + const cssFigureStyles = [ styles.euiImage, float && styles[float], margin && styles[margin], allowFullScreen && styles.allowFullScreen, - isFullScreenActive && styles.isFullScreenActive, ]; - const imgStyles = euiImageImgStyles(euiTheme); - let cssImgStyles; - - if (typeof size === 'string' && SIZES.includes(size as EuiImageSize)) { - cssImgStyles = [ - imgStyles.euiImage__img, - imgStyles[size as EuiImageSize], - hasShadow && imgStyles.hasShadow, - ]; - } else { - cssImgStyles = [imgStyles.euiImage__img, hasShadow && imgStyles.hasShadow]; - cssStyles.push(styles.customSizeStyle); - } + const cssFigureFullScreen = [ + styles.euiImage, + isFullScreenActive && styles.isFullScreen, + ]; + + const imgStyles = euiImageImgStyles(euiTheme, size); + const cssImgStyles = [ + imgStyles.euiImage__img, + isNamedSize && imgStyles[size as EuiImageSize], + !isNamedSize && imgStyles.customSize, + hasShadow && imgStyles.hasShadow, + ]; const buttonStyles = euiImageButtonStyles(euiTheme); const cssButtonStyles = [ buttonStyles.euiImage__button, - // when the button is not custom we need it to go full width - // to match the parent '.euiImage' width except when the size is original - typeof size === 'string' && + // when the image button is not in full screen mode and the size is not custom + // we need the image button to go full width to match the parent '.euiImage' + // width except when the size is `original` + !isFullScreenActive && + isNamedSize && size !== 'original' && - SIZES.includes(size as EuiImageSize) && buttonStyles.fullWidth, ]; @@ -183,8 +182,8 @@ export const EuiImage: FunctionComponent = ({ const iconStyles = euiImageIconStyles(euiTheme); const cssIconStyles = [iconStyles.euiImage__icon]; - const fullScreenImgStyles = euiImageFullScreenImgStyles(euiTheme); - const cssFullScreenImgStyles = [fullScreenImgStyles.euiImage__fullScreenImg]; + const fullScreenImgStyles = euiImageFullScreenImgStyles(); + const cssFullScreenImgStyles = [fullScreenImgStyles.euiImage__fullScreen]; const fullScreenCloseIconStyles = euiImageFullScreenCloseIconStyles(euiTheme); const cssFullScreenCloseIconStyles = [ @@ -216,7 +215,7 @@ export const EuiImage: FunctionComponent = ({ > <> -
+
- {isFullScreenActive && fullScreenDisplay} - {optionalCaption} -
- ); - } else { - return ( -
- {img} - {optionalCaption} -
- ); - } + ) : ( + imageDisplay + )} + + {isFullScreenActive && fullScreenDisplay} + {optionalCaption} +
+ ); }; From ecb63f5b669a6cb11f64c9249741b6da017bae54 Mon Sep 17 00:00:00 2001 From: Elizabet Oliveira Date: Wed, 22 Jun 2022 14:08:26 +0100 Subject: [PATCH 06/48] Adding _imageMargins style function --- src/components/image/image.styles.ts | 163 +++++++++++++++++++-------- src/components/image/image.tsx | 20 +++- 2 files changed, 130 insertions(+), 53 deletions(-) diff --git a/src/components/image/image.styles.ts b/src/components/image/image.styles.ts index 8c0d13f6378..917b641c680 100644 --- a/src/components/image/image.styles.ts +++ b/src/components/image/image.styles.ts @@ -13,21 +13,64 @@ import { euiCanAnimate, logicalCSS, } from '../../global_styling'; -import { - UseEuiTheme, - isWithinBreakpoints, - transparentize, -} from '../../services'; +import { UseEuiTheme, transparentize } from '../../services'; import { euiShadow } from '../../themes/amsterdam/global_styling/mixins'; import type { EuiImageSize } from './image'; const _convertToRem = (size: number) => { - return `${size / 16}rem`; + return `${size / 16}rem !important`; +}; + +const _imageMargins = ({ + size, + hasFloatLeft, + hasFloatRight, + isSmallScreen, +}: { + size: string; + hasFloatLeft: boolean | undefined; + hasFloatRight: boolean | undefined; + isSmallScreen: boolean; +}) => { + const hasFloat = hasFloatLeft || hasFloatRight; + + let mainStyles; + + if (hasFloat && isSmallScreen) { + mainStyles = ` + ${logicalCSS('margin-horizontal', 'inherit')}; + ${logicalCSS('margin-vertical', 'inherit')}; + `; + } else { + mainStyles = ` + ${logicalCSS('margin-horizontal', size)}; + ${logicalCSS('margin-vertical', size)}; + `; + } + + const floatLeftStyles = ` + ${logicalCSS('margin-left', '0')}; + ${logicalCSS('margin-top', '0')}; + `; + + const floatRightStyles = ` + ${logicalCSS('margin-right', '0')}; + ${logicalCSS('margin-top', '0')}; + `; + + return ` + ${mainStyles} + ${floatLeftStyles}; + ${floatRightStyles}; + `; }; export const euiImageStyles = ( euiThemeContext: UseEuiTheme, - hasShadow: boolean | undefined + hasShadow: boolean | undefined, + hasFloatLeft: boolean | undefined, + hasFloatRight: boolean | undefined, + isSmallScreen: boolean ) => { const { euiTheme } = euiThemeContext; @@ -37,21 +80,8 @@ export const euiImageStyles = ( display: inline-block; max-width: 100%; position: relative; - min-height: 1px; /* 1 */ line-height: 0; // Fixes cropping when image is resized by forcing its height to be determined by the image not line-height flex-shrink: 0; // Don't ever let this shrink in height if direct descendent of flex - - ${isWithinBreakpoints(window.innerWidth, ['xs', 's', 'm'])} { - &[class*='-left'], - &[class*='-right'] { - float: none; - // Return back to whatever margin settings were set without the float - margin-top: inherit; - margin-right: inherit; - margin-bottom: inherit; - margin-left: inherit; - } - } `, allowFullScreen: css` &:hover .euiImage__caption { @@ -62,15 +92,17 @@ export const euiImageStyles = ( width: 100%; } - &:not([class*='-hasShadow']) [class*='euiImage__button']:hover, - &:not([class*='-hasShadow']) [class*='euiImage__button']:focus { - ${euiShadow(euiThemeContext, 'm')}; - } - - &.euiImage--hasShadow .euiImage__button:hover, - &.euiImage--hasShadow .euiImage__button:focus { - ${euiShadow(euiThemeContext, 's')}; - } + ${hasShadow + ? ` + [class*='euiImage__button']:hover, + [class*='euiImage__button']:focus { + ${euiShadow(euiThemeContext, 'm')}; + }` + : ` + [class*='euiImage__button']:hover, + [class*='euiImage__button']:focus { + ${euiShadow(euiThemeContext, 's')}; + }`} `, isFullScreen: css` position: relative; @@ -97,28 +129,57 @@ export const euiImageStyles = ( } `, // margins - s: css` - margin: ${euiTheme.size.s}; - `, - m: css` - margin: ${euiTheme.size.base}; - `, - l: css` - margin: ${euiTheme.size.l}; - `, - xl: css` - margin: ${euiTheme.size.xl}; - `, + s: css( + _imageMargins({ + size: euiTheme.size.s, + hasFloatLeft, + hasFloatRight, + isSmallScreen, + }) + ), + m: css( + _imageMargins({ + size: euiTheme.size.base, + hasFloatLeft, + hasFloatRight, + isSmallScreen, + }) + ), + l: css( + _imageMargins({ + size: euiTheme.size.l, + hasFloatLeft, + hasFloatRight, + isSmallScreen, + }) + ), + xl: css( + _imageMargins({ + size: euiTheme.size.xl, + hasFloatLeft, + hasFloatRight, + isSmallScreen, + }) + ), // floats left: css` - float: left; - margin-left: 0 !important; - margin-top: 0 !important; + ${isSmallScreen + ? ` + float: none; + ` + : ` + float: left; + + `} `, right: css` - float: right; - margin-right: 0 !important; - margin-top: 0 !important; + ${isSmallScreen + ? ` + float: none; + ` + : ` + float: right; + `} `, }; }; @@ -132,9 +193,13 @@ export const euiImageImgStyles = ( euiImage__img: css` width: 100%; vertical-align: middle; - // Required for common usage of nesting within EuiText - margin-bottom: 0 !important; max-width: 100%; + + &, + // Required for common usage of nesting within EuiText + [class*='euiText'] & { + ${logicalCSS('margin-bottom', 0)}; + } `, hasShadow: css` ${euiShadow(euiThemeContext, 's')}; diff --git a/src/components/image/image.tsx b/src/components/image/image.tsx index 99e96d8038b..db6ec9e70f1 100644 --- a/src/components/image/image.tsx +++ b/src/components/image/image.tsx @@ -23,7 +23,7 @@ import { useEuiI18n } from '../i18n'; import { EuiFocusTrap } from '../focus_trap'; -import { keys, useEuiTheme } from '../../services'; +import { keys, useEuiTheme, useIsWithinBreakpoints } from '../../services'; import { useInnerText } from '../inner_text'; import { euiImageStyles, @@ -122,6 +122,9 @@ export const EuiImage: FunctionComponent = ({ const [isFullScreenActive, setIsFullScreenActive] = useState(false); const isNamedSize = typeof size === 'string' && SIZES.includes(size as EuiImageSize); + const isSmallScreen = useIsWithinBreakpoints(['xs', 's', 'm']); + const hasFloatLeft = float === 'left'; + const hasFloatRight = float === 'right'; const onKeyDown = (event: React.KeyboardEvent) => { if (event.key === keys.ESCAPE) { @@ -143,7 +146,13 @@ export const EuiImage: FunctionComponent = ({ const euiTheme = useEuiTheme(); - const styles = euiImageStyles(euiTheme, hasShadow); + const styles = euiImageStyles( + euiTheme, + hasShadow, + hasFloatLeft, + hasFloatRight, + isSmallScreen + ); const cssFigureStyles = [ styles.euiImage, float && styles[float], @@ -151,7 +160,7 @@ export const EuiImage: FunctionComponent = ({ allowFullScreen && styles.allowFullScreen, ]; - const cssFigureFullScreen = [ + const cssFullScreenFigureStyles = [ styles.euiImage, isFullScreenActive && styles.isFullScreen, ]; @@ -215,7 +224,10 @@ export const EuiImage: FunctionComponent = ({ > <> -
+
- {optionalCaption} -
- - - - - ); - - const fullscreenLabel = useEuiI18n( - 'euiImage.openImage', - 'Open fullscreen {alt} image', - { alt } - ); - - const imageDisplay = ( - {alt} - ); + const cssStyles = isFullScreen + ? [imgStyles.fullScreen] + : [ + imgStyles.euiImage, + isNamedSize && imgStyles[size as EuiImageSize], + !isNamedSize && imgStyles.customSize, + hasShadow && imgStyles.hasShadow, + ]; return ( -
- {allowFullScreen ? ( - - ) : ( - imageDisplay - )} - - {isFullScreenActive && fullScreenDisplay} - {optionalCaption} -
+ {rest.alt} + ); }; diff --git a/src/components/image/image_wrapper.styles.ts b/src/components/image/image_wrapper.styles.ts new file mode 100644 index 00000000000..d60d085437c --- /dev/null +++ b/src/components/image/image_wrapper.styles.ts @@ -0,0 +1,255 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0 and the Server Side Public License, v 1; you may not use this file except + * in compliance with, at your election, the Elastic License 2.0 or the Server + * Side Public License, v 1. + */ + +import { css, keyframes } from '@emotion/react'; +import { + euiFocusRing, + euiFontSize, + logicalCSS, + euiCanAnimate, +} from '../../global_styling'; +import { UseEuiTheme, transparentize } from '../../services'; +import { euiShadow } from '../../themes/amsterdam/global_styling/mixins'; + +const _imageMargins = ({ + size, + hasFloatLeft, + hasFloatRight, + isSmallScreen, +}: { + size: string; + hasFloatLeft: boolean | undefined; + hasFloatRight: boolean | undefined; + isSmallScreen: boolean; +}) => { + const hasFloat = hasFloatLeft || hasFloatRight; + + let mainStyles; + + if (hasFloat && isSmallScreen) { + mainStyles = ` + ${logicalCSS('margin-horizontal', 'inherit')}; + ${logicalCSS('margin-vertical', 'inherit')}; + `; + } else { + mainStyles = ` + ${logicalCSS('margin-horizontal', size)}; + ${logicalCSS('margin-vertical', size)}; + `; + } + + const floatLeftStyles = ` + ${logicalCSS('margin-left', '0')}; + ${logicalCSS('margin-top', '0')}; + `; + + const floatRightStyles = ` + ${logicalCSS('margin-right', '0')}; + ${logicalCSS('margin-top', '0')}; + `; + + return ` + ${mainStyles} + ${hasFloatLeft && floatLeftStyles}; + ${hasFloatRight && floatRightStyles}; + `; +}; + +export const euiImageWrapperStyles = ( + euiThemeContext: UseEuiTheme, + hasShadow: boolean | undefined, + hasFloatLeft: boolean | undefined, + hasFloatRight: boolean | undefined, + isSmallScreen: boolean +) => { + const { euiTheme } = euiThemeContext; + + return { + // Base + euiImageWrapper: css` + display: inline-block; + max-width: 100%; + position: relative; + line-height: 0; // Fixes cropping when image is resized by forcing its height to be determined by the image not line-height + flex-shrink: 0; // Don't ever let this shrink in height if direct descendent of flex + `, + allowFullScreen: css` + &:hover .euiImageWrapper__caption { + text-decoration: underline; + } + + &[class*='-fullWidth'] { + width: 100%; + } + + ${hasShadow + ? ` + [class*='euiImageWrapper__button']:hover, + [class*='euiImageWrapper__button']:focus { + ${euiShadow(euiThemeContext, 'm')}; + }` + : ` + [class*='euiImageWrapper__button']:hover, + [class*='euiImageWrapper__button']:focus { + ${euiShadow(euiThemeContext, 's')}; + }`} + `, + isFullScreen: css` + position: relative; + max-height: 80vh; + max-width: 80vw; + ${euiCanAnimate} { + animation: ${euiImageFullScreen(euiTheme.size.xxxxl)} + ${euiTheme.animation.extraSlow} ${euiTheme.animation.bounce}; + } + + [class*='euiImageWrapper__caption'] { + color: ${euiTheme.colors.ghost}; + text-shadow: 0 1px 2px ${transparentize(euiTheme.colors.ink, 0.6)}; + } + + &:hover { + [class*='euiImageWrapper__button'] { + ${euiShadow(euiThemeContext, 's')}; + } + + [class*='euiImageWrapper__caption'] { + text-decoration: underline; + } + } + `, + // margins + s: css( + _imageMargins({ + size: euiTheme.size.s, + hasFloatLeft, + hasFloatRight, + isSmallScreen, + }) + ), + m: css( + _imageMargins({ + size: euiTheme.size.base, + hasFloatLeft, + hasFloatRight, + isSmallScreen, + }) + ), + l: css( + _imageMargins({ + size: euiTheme.size.l, + hasFloatLeft, + hasFloatRight, + isSmallScreen, + }) + ), + xl: css( + _imageMargins({ + size: euiTheme.size.xl, + hasFloatLeft, + hasFloatRight, + isSmallScreen, + }) + ), + // floats + left: css` + ${isSmallScreen + ? ` + float: none; + ` + : ` + float: left; + `} + `, + right: css` + ${isSmallScreen + ? ` + float: none; + ` + : ` + float: right; + `} + `, + }; +}; + +export const euiImageWrapperButtonStyles = ({ euiTheme }: UseEuiTheme) => ({ + // Base + euiImageWrapper__button: css` + position: relative; + cursor: pointer; + line-height: 0; + + // transition the shadow + transition: all ${euiTheme.animation.fast} ${euiTheme.animation.resistance}; + + &:focus { + ${euiFocusRing(euiTheme, 'outset')} + } + + &:hover [class*='euiImageWrapper__icon'] { + visibility: visible; + fill-opacity: 1; + } + `, + fullWidth: css` + width: 100%; + `, +}); + +export const euiImageWrapperCaptionStyles = (euiThemeContext: UseEuiTheme) => { + const { euiTheme } = euiThemeContext; + + return { + // Base + euiImageWrapper__caption: css` + ${euiFontSize(euiThemeContext, 's')}; + ${logicalCSS('margin-top', euiTheme.size.xs)}; + text-align: center; + `, + }; +}; + +export const euiImageWrapperIconStyles = ({ euiTheme }: UseEuiTheme) => ({ + // Base + euiImageWrapper__icon: css` + visibility: hidden; + fill-opacity: 0; + position: absolute; + ${logicalCSS('top', euiTheme.size.base)}; + ${logicalCSS('right', euiTheme.size.base)}; + cursor: pointer; + transition: fill-opacity ${euiTheme.animation.slow} + ${euiTheme.animation.resistance}; + `, +}); + +export const euiImageWrapperFullScreenCloseIconStyles = ({ + euiTheme, +}: UseEuiTheme) => ({ + // Base + euiImageWrapper__fullScreenCloseIcon: css` + position: absolute; + ${logicalCSS('top', euiTheme.size.base)}; + ${logicalCSS('right', euiTheme.size.base)}; + pointer-events: none; + fill: ${euiTheme.colors.ghost} !important; + `, +}); + +const euiImageFullScreen = (size: string) => keyframes` + 0% { + opacity: 0; + transform: translateY(${size}); + } + + 100% { + opacity: 1; + transform: translateY(0); + } +`; diff --git a/src/components/image/image_wrapper.tsx b/src/components/image/image_wrapper.tsx new file mode 100644 index 00000000000..7428985027b --- /dev/null +++ b/src/components/image/image_wrapper.tsx @@ -0,0 +1,272 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0 and the Server Side Public License, v 1; you may not use this file except + * in compliance with, at your election, the Elastic License 2.0 or the Server + * Side Public License, v 1. + */ + +import React, { FunctionComponent, HTMLAttributes, ReactNode } from 'react'; +import classNames from 'classnames'; + +import { CommonProps } from '../common'; + +import { EuiIcon } from '../../components/icon'; +import { EuiFocusTrap } from '../../components/focus_trap'; +import { EuiOverlayMask } from '../../components/overlay_mask'; +import { useEuiI18n } from '../i18n'; + +import { keys, useEuiTheme, useIsWithinBreakpoints } from '../../services'; +import { useInnerText } from '../inner_text'; +import { + euiImageWrapperStyles, + euiImageWrapperButtonStyles, + euiImageWrapperCaptionStyles, + euiImageWrapperIconStyles, + euiImageWrapperFullScreenCloseIconStyles, +} from './image_wrapper.styles'; + +export const SIZES = ['s', 'm', 'l', 'xl', 'fullWidth', 'original'] as const; +export type EuiImageWrapperSize = typeof SIZES[number]; + +const FLOATS = ['left', 'right'] as const; +export type EuiImageWrapperFloat = typeof FLOATS[number]; + +const MARGINS = ['s', 'm', 'l', 'xl'] as const; +export type EuiImageWrapperMargin = typeof MARGINS[number]; + +export type EuiImageWrapperFullScreenIconColor = 'light' | 'dark'; + +const fullScreenIconColorMap: { + [color in EuiImageWrapperFullScreenIconColor]: string; +} = { + light: 'ghost', + dark: 'default', +}; + +export type EuiImageWrapperProps = CommonProps & { + /** + * Separate from the caption is a title on the alt tag itself. + * This one is required for accessibility. + */ + alt: string; + /** + * Accepts `s` / `m` / `l` / `xl` / `original` / `fullWidth` / or a CSS size of `number` or `string`. + * `fullWidth` will set the figure to stretch to 100% of its container. + * `string` and `number` types will max both the width or height, whichever is greater. + */ + size?: EuiImageWrapperSize | number | string; + /** + * Changes the color of the icon that floats above the image when it can be clicked to fullscreen. + * The default value of `light` is fine unless your image has a white background, in which case you should change it to `dark`. + */ + fullScreenIconColor?: EuiImageWrapperFullScreenIconColor; + /** + * Provides the visible caption to the image + */ + caption?: ReactNode; + /** + * When set to `true` (default) will apply a slight shadow to the image + */ + hasShadow?: boolean; + /** + * When set to `true` will make the image clickable to a larger version + */ + allowFullScreen?: boolean; + /** + * Float the image to the left or right. Useful in large text blocks. + */ + float?: EuiImageWrapperFloat; + /** + * Margin around the image. + */ + margin?: EuiImageWrapperMargin; + /** + * Props to add to the wrapping `.euiImageWrapper` figure + */ + wrapperProps?: HTMLAttributes; + isFullScreen: boolean; + setIsFullScreen: (isFullScreen: boolean) => void; +}; + +export const EuiImageWrapper: FunctionComponent = ({ + size = 'original', + caption, + hasShadow, + allowFullScreen, + fullScreenIconColor = 'light', + alt, + float, + margin, + children, + isFullScreen, + setIsFullScreen, + wrapperProps, +}) => { + const isNamedSize = + typeof size === 'string' && SIZES.includes(size as EuiImageWrapperSize); + const isSmallScreen = useIsWithinBreakpoints(['xs', 's', 'm']); + const hasFloatLeft = float === 'left'; + const hasFloatRight = float === 'right'; + + const onKeyDown = (event: React.KeyboardEvent) => { + if (event.key === keys.ESCAPE) { + event.preventDefault(); + event.stopPropagation(); + closeFullScreen(); + } + }; + + const closeFullScreen = () => { + setIsFullScreen(false); + }; + + const openFullScreen = () => { + setIsFullScreen(true); + }; + + const classes = classNames( + 'euiImageWrapper', + wrapperProps && wrapperProps.className + ); + + const euiTheme = useEuiTheme(); + + const styles = euiImageWrapperStyles( + euiTheme, + hasShadow, + hasFloatLeft, + hasFloatRight, + isSmallScreen + ); + const cssFigureStyles = [ + styles.euiImageWrapper, + float && styles[float], + margin && styles[margin], + allowFullScreen && styles.allowFullScreen, + ]; + + const cssFullScreenFigureStyles = [ + styles.euiImageWrapper, + isFullScreen && styles.isFullScreen, + ]; + + const buttonStyles = euiImageWrapperButtonStyles(euiTheme); + const cssButtonStyles = [ + buttonStyles.euiImageWrapper__button, + // when the image button is not in full screen mode and the size is not custom + // we need the image button to go full width to match the parent '.euiImage' + // width except when the size is `original` + !isFullScreen && + isNamedSize && + size !== 'original' && + buttonStyles.fullWidth, + ]; + + const captionStyles = euiImageWrapperCaptionStyles(euiTheme); + const cssCaptionStyles = [captionStyles.euiImageWrapper__caption]; + + const iconStyles = euiImageWrapperIconStyles(euiTheme); + const cssIconStyles = [iconStyles.euiImageWrapper__icon]; + + const fullScreenCloseIconStyles = euiImageWrapperFullScreenCloseIconStyles( + euiTheme + ); + const cssFullScreenCloseIconStyles = [ + fullScreenCloseIconStyles.euiImageWrapper__fullScreenCloseIcon, + ]; + + const [optionalCaptionRef, optionalCaptionText] = useInnerText(); + let optionalCaption; + if (caption) { + optionalCaption = ( +
+ {caption} +
+ ); + } + + const allowFullScreenIcon = ( + + ); + + const fullscreenLabel = useEuiI18n( + 'euiImageWrapper.openImage', + 'Open fullscreen {alt} image', + { alt } + ); + + const fullScreen = ( + + + <> +
+ + {optionalCaption} +
+ + +
+
+ ); + + return ( +
+ {allowFullScreen ? ( + + ) : ( + children + )} + + {isFullScreen && fullScreen} + {optionalCaption} +
+ ); +}; From cba8d5ad4b23ccad1c3a07c50b6ed4e90ed05018 Mon Sep 17 00:00:00 2001 From: Elizabet Oliveira Date: Tue, 28 Jun 2022 17:00:53 +0100 Subject: [PATCH 10/48] Improving `EuiText` image styles --- .../datagrid/body/__snapshots__/data_grid_cell.test.tsx.snap | 2 +- .../modal/__snapshots__/confirm_modal.test.tsx.snap | 2 +- src/components/text/__snapshots__/text.test.tsx.snap | 4 ++-- src/components/text/text.styles.ts | 2 +- .../tree_view/__snapshots__/tree_view.test.tsx.snap | 4 ++-- upcoming_changelogs/5969.md | 2 ++ 6 files changed, 9 insertions(+), 7 deletions(-) diff --git a/src/components/datagrid/body/__snapshots__/data_grid_cell.test.tsx.snap b/src/components/datagrid/body/__snapshots__/data_grid_cell.test.tsx.snap index 2dd1c510819..5dd0b916b82 100644 --- a/src/components/datagrid/body/__snapshots__/data_grid_cell.test.tsx.snap +++ b/src/components/datagrid/body/__snapshots__/data_grid_cell.test.tsx.snap @@ -3,7 +3,7 @@ exports[`EuiDataGridCell componentDidUpdate handles the cell popover by forwarding the cell's DOM node and contents to the parent popover context 1`] = ` Array [
`; diff --git a/src/components/image/image.styles.ts b/src/components/image/image.styles.ts index dff47e9f8df..921efd2fd1a 100644 --- a/src/components/image/image.styles.ts +++ b/src/components/image/image.styles.ts @@ -10,65 +10,50 @@ import { css } from '@emotion/react'; import { logicalCSS } from '../../global_styling'; import { UseEuiTheme } from '../../services'; import { euiShadow } from '../../themes/amsterdam/global_styling/mixins'; -import type { EuiImageSize } from './image'; +export const euiImageStyles = (euiThemeContext: UseEuiTheme) => ({ + euiImage: css` + vertical-align: middle; + max-width: 100%; -const _imageWidth = (euiTheme: UseEuiTheme['euiTheme'], size: number) => { - const width = `${size / euiTheme.base}rem`; - - return ` &, // Required for common usage of nesting within EuiText [class*='euiText'] & { - width: ${width}; + ${logicalCSS('margin-bottom', 0)}; } - `; -}; - -export const euiImageStyles = ( - euiThemeContext: UseEuiTheme, - size: EuiImageSize | number | string -) => { - const { euiTheme } = euiThemeContext; - - return { - // The image itself is full width within the container. - euiImage: css` - width: 100%; - vertical-align: middle; - max-width: 100%; - - &, - // Required for common usage of nesting within EuiText - [class*='euiText'] & { - ${logicalCSS('margin-bottom', 0)}; - } - `, - hasShadow: css` - ${euiShadow(euiThemeContext, 's')}; - `, - // Sizes - // These sizes are mostly suggestions. Don't look too hard for meaning in their values. - // Size is applied to the image, rather than the figure to work better with floats - s: css(_imageWidth(euiTheme, 120)), - m: css(_imageWidth(euiTheme, 200)), - l: css(_imageWidth(euiTheme, 360)), - xl: css(_imageWidth(euiTheme, 600)), - original: css` - width: auto; - max-width: 100%; - `, + `, + hasShadow: css` + ${euiShadow(euiThemeContext, 's')}; + `, + // Sizes + // These sizes are mostly suggestions. Don't look too hard for meaning in their values. + // Size is applied to the image, rather than the wrapper figure to work better with floats + s: css` + width: 100px; + `, + m: css` + width: 200px; + `, + l: css` + width: 360px; + `, + xl: css` + width: 600px; + `, + original: css` + width: auto; + `, - fullWidth: css``, - customSize: css` - max-width: ${typeof size === 'string' ? size : `${size}px`}; - max-height: ${typeof size === 'string' ? size : `${size}px`}; - // Set width back to auto to ensure aspect ratio is kept - width: auto; - `, - fullScreen: css` - position: relative; - max-height: 80vh; - max-width: 80vw; - `, - }; -}; + fullWidth: css` + width: 100%; + `, + customSize: css` + // A custom max-width and max-height is set in the style tag + // We set the width back to auto to ensure aspect ratio is kept + width: auto; + `, + fullScreen: css` + position: relative; + max-height: 80vh; + max-width: 80vw; + `, +}); diff --git a/src/components/image/image.tsx b/src/components/image/image.tsx index 37df9392382..58dbc28b4b5 100644 --- a/src/components/image/image.tsx +++ b/src/components/image/image.tsx @@ -50,7 +50,7 @@ export const EuiImage: FunctionComponent = ({ src, size = 'original', hasShadow, - fullScreenIconColor, + style, wrapperProps, ...rest }) => { @@ -63,7 +63,7 @@ export const EuiImage: FunctionComponent = ({ const euiTheme = useEuiTheme(); - const imgStyles = euiImageStyles(euiTheme, size); + const imgStyles = euiImageStyles(euiTheme); const cssStyles = isFullScreen ? [imgStyles.fullScreen] @@ -74,21 +74,34 @@ export const EuiImage: FunctionComponent = ({ hasShadow && imgStyles.hasShadow, ]; + const isCustomSize = !isFullScreen && !isNamedSize && size !== 'original'; + + const customSize = typeof size === 'string' ? size : `${size}px`; + + const styles = isCustomSize + ? { + ...style, + maxWidth: customSize, + maxHeight: customSize, + } + : style; + return ( {rest.alt} ); diff --git a/src/components/image/image_wrapper.styles.ts b/src/components/image/image_wrapper.styles.ts index d60d085437c..633d7d1b3fa 100644 --- a/src/components/image/image_wrapper.styles.ts +++ b/src/components/image/image_wrapper.styles.ts @@ -62,7 +62,6 @@ const _imageMargins = ({ export const euiImageWrapperStyles = ( euiThemeContext: UseEuiTheme, - hasShadow: boolean | undefined, hasFloatLeft: boolean | undefined, hasFloatRight: boolean | undefined, isSmallScreen: boolean @@ -79,48 +78,22 @@ export const euiImageWrapperStyles = ( flex-shrink: 0; // Don't ever let this shrink in height if direct descendent of flex `, allowFullScreen: css` - &:hover .euiImageWrapper__caption { + &:hover [class*='euiImageWrapper__caption'] { text-decoration: underline; } - - &[class*='-fullWidth'] { - width: 100%; - } - - ${hasShadow - ? ` - [class*='euiImageWrapper__button']:hover, - [class*='euiImageWrapper__button']:focus { - ${euiShadow(euiThemeContext, 'm')}; - }` - : ` - [class*='euiImageWrapper__button']:hover, - [class*='euiImageWrapper__button']:focus { - ${euiShadow(euiThemeContext, 's')}; - }`} `, isFullScreen: css` position: relative; max-height: 80vh; max-width: 80vw; + ${euiCanAnimate} { animation: ${euiImageFullScreen(euiTheme.size.xxxxl)} ${euiTheme.animation.extraSlow} ${euiTheme.animation.bounce}; } - [class*='euiImageWrapper__caption'] { - color: ${euiTheme.colors.ghost}; - text-shadow: 0 1px 2px ${transparentize(euiTheme.colors.ink, 0.6)}; - } - - &:hover { - [class*='euiImageWrapper__button'] { - ${euiShadow(euiThemeContext, 's')}; - } - - [class*='euiImageWrapper__caption'] { - text-decoration: underline; - } + &:hover [class*='euiImageWrapper__caption'] { + text-decoration: underline; } `, // margins @@ -175,32 +148,52 @@ export const euiImageWrapperStyles = ( float: right; `} `, + // sizes + fullWidth: css` + width: 100%; + `, }; }; -export const euiImageWrapperButtonStyles = ({ euiTheme }: UseEuiTheme) => ({ - // Base - euiImageWrapper__button: css` - position: relative; - cursor: pointer; - line-height: 0; +export const euiImageWrapperButtonStyles = ( + euiThemeContext: UseEuiTheme, + hasShadow: boolean | undefined +) => { + const { euiTheme } = euiThemeContext; + + return { + // Base + euiImageWrapper__button: css` + position: relative; + cursor: pointer; + line-height: 0; + + &:hover, + &:focus { + ${hasShadow + ? `${euiShadow(euiThemeContext, 'm')};` + : `${euiShadow(euiThemeContext, 's')};`} + } - // transition the shadow - transition: all ${euiTheme.animation.fast} ${euiTheme.animation.resistance}; + ${euiCanAnimate} { + transition: box-shadow ${euiTheme.animation.fast} + ${euiTheme.animation.resistance}; + } - &:focus { - ${euiFocusRing(euiTheme, 'outset')} - } + &:focus { + ${euiFocusRing(euiTheme, 'outset')} + } - &:hover [class*='euiImageWrapper__icon'] { - visibility: visible; - fill-opacity: 1; - } - `, - fullWidth: css` - width: 100%; - `, -}); + &:hover [class*='euiImageWrapper__icon'] { + visibility: visible; + fill-opacity: 1; + } + `, + fullWidth: css` + width: 100%; + `, + }; +}; export const euiImageWrapperCaptionStyles = (euiThemeContext: UseEuiTheme) => { const { euiTheme } = euiThemeContext; @@ -212,6 +205,10 @@ export const euiImageWrapperCaptionStyles = (euiThemeContext: UseEuiTheme) => { ${logicalCSS('margin-top', euiTheme.size.xs)}; text-align: center; `, + isFullScreen: css` + color: ${euiTheme.colors.ghost}; + text-shadow: 0 1px 2px ${transparentize(euiTheme.colors.ink, 0.6)}; + `, }; }; @@ -224,8 +221,11 @@ export const euiImageWrapperIconStyles = ({ euiTheme }: UseEuiTheme) => ({ ${logicalCSS('top', euiTheme.size.base)}; ${logicalCSS('right', euiTheme.size.base)}; cursor: pointer; - transition: fill-opacity ${euiTheme.animation.slow} - ${euiTheme.animation.resistance}; + + ${euiCanAnimate} { + transition: fill-opacity ${euiTheme.animation.slow} + ${euiTheme.animation.resistance}; + } `, }); diff --git a/src/components/image/image_wrapper.tsx b/src/components/image/image_wrapper.tsx index 7428985027b..fd2824069ed 100644 --- a/src/components/image/image_wrapper.tsx +++ b/src/components/image/image_wrapper.tsx @@ -103,8 +103,6 @@ export const EuiImageWrapper: FunctionComponent = ({ setIsFullScreen, wrapperProps, }) => { - const isNamedSize = - typeof size === 'string' && SIZES.includes(size as EuiImageWrapperSize); const isSmallScreen = useIsWithinBreakpoints(['xs', 's', 'm']); const hasFloatLeft = float === 'left'; const hasFloatRight = float === 'right'; @@ -134,7 +132,6 @@ export const EuiImageWrapper: FunctionComponent = ({ const styles = euiImageWrapperStyles( euiTheme, - hasShadow, hasFloatLeft, hasFloatRight, isSmallScreen @@ -144,6 +141,7 @@ export const EuiImageWrapper: FunctionComponent = ({ float && styles[float], margin && styles[margin], allowFullScreen && styles.allowFullScreen, + size === 'fullWidth' && styles.fullWidth, ]; const cssFullScreenFigureStyles = [ @@ -151,20 +149,17 @@ export const EuiImageWrapper: FunctionComponent = ({ isFullScreen && styles.isFullScreen, ]; - const buttonStyles = euiImageWrapperButtonStyles(euiTheme); + const buttonStyles = euiImageWrapperButtonStyles(euiTheme, hasShadow); const cssButtonStyles = [ buttonStyles.euiImageWrapper__button, - // when the image button is not in full screen mode and the size is not custom - // we need the image button to go full width to match the parent '.euiImage' - // width except when the size is `original` - !isFullScreen && - isNamedSize && - size !== 'original' && - buttonStyles.fullWidth, + !isFullScreen && size === 'fullWidth' && buttonStyles.fullWidth, ]; const captionStyles = euiImageWrapperCaptionStyles(euiTheme); - const cssCaptionStyles = [captionStyles.euiImageWrapper__caption]; + const cssCaptionStyles = [ + captionStyles.euiImageWrapper__caption, + isFullScreen && captionStyles.isFullScreen, + ]; const iconStyles = euiImageWrapperIconStyles(euiTheme); const cssIconStyles = [iconStyles.euiImageWrapper__icon]; @@ -188,13 +183,13 @@ export const EuiImageWrapper: FunctionComponent = ({ const allowFullScreenIcon = ( ); @@ -234,7 +229,7 @@ export const EuiImageWrapper: FunctionComponent = ({ {optionalCaption}
diff --git a/upcoming_changelogs/5969.md b/upcoming_changelogs/5969.md index 92fd1f0599f..476fdb1406c 100644 --- a/upcoming_changelogs/5969.md +++ b/upcoming_changelogs/5969.md @@ -1,4 +1,4 @@ -- Updated styles for the `EuiText.img` to prevent them from unnecessarily growing full width +- Updated `EuiText.img` styles to prevent images from growing full width **CSS-in-JS** From 4cb189347a705dac1baf679651fd361168d2d632 Mon Sep 17 00:00:00 2001 From: Elizabet Oliveira Date: Wed, 29 Jun 2022 19:21:41 +0100 Subject: [PATCH 12/48] Adding `shouldRenderCustomStyles` --- src/components/image/image.test.tsx | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/components/image/image.test.tsx b/src/components/image/image.test.tsx index cb19bc77194..f8349760afc 100644 --- a/src/components/image/image.test.tsx +++ b/src/components/image/image.test.tsx @@ -11,12 +11,12 @@ import { render, mount, ReactWrapper } from 'enzyme'; import { requiredProps, findTestSubject } from '../../test'; import { act } from 'react-dom/test-utils'; import { keys } from '../../services'; -// import { shouldRenderCustomStyles } from '../../test/internal'; +import { shouldRenderCustomStyles } from '../../test/internal'; import { EuiImage } from './image'; describe('EuiImage', () => { - // shouldRenderCustomStyles(); + shouldRenderCustomStyles(); test('is rendered', () => { const component = render( From 50ef8b2938872ee6c8e9bc6a35660901e9c9f92a Mon Sep 17 00:00:00 2001 From: Elizabet Oliveira Date: Thu, 30 Jun 2022 12:45:25 +0100 Subject: [PATCH 13/48] Renaming styles --- src/components/image/image.tsx | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/src/components/image/image.tsx b/src/components/image/image.tsx index 58dbc28b4b5..b9e88a7536e 100644 --- a/src/components/image/image.tsx +++ b/src/components/image/image.tsx @@ -63,22 +63,22 @@ export const EuiImage: FunctionComponent = ({ const euiTheme = useEuiTheme(); - const imgStyles = euiImageStyles(euiTheme); + const styles = euiImageStyles(euiTheme); const cssStyles = isFullScreen - ? [imgStyles.fullScreen] + ? [styles.fullScreen] : [ - imgStyles.euiImage, - isNamedSize && imgStyles[size as EuiImageSize], - !isNamedSize && imgStyles.customSize, - hasShadow && imgStyles.hasShadow, + styles.euiImage, + isNamedSize && styles[size as EuiImageSize], + !isNamedSize && styles.customSize, + hasShadow && styles.hasShadow, ]; const isCustomSize = !isFullScreen && !isNamedSize && size !== 'original'; const customSize = typeof size === 'string' ? size : `${size}px`; - const styles = isCustomSize + const imageStyle = isCustomSize ? { ...style, maxWidth: customSize, @@ -97,7 +97,7 @@ export const EuiImage: FunctionComponent = ({ > {rest.alt} Date: Thu, 30 Jun 2022 16:24:19 +0100 Subject: [PATCH 14/48] splitting in more components --- .../image/__snapshots__/image.test.tsx.snap | 26 ++- src/components/image/image.styles.ts | 19 +- src/components/image/image.tsx | 31 ++- src/components/image/image_button.styles.ts | 69 +++++++ src/components/image/image_button.tsx | 91 +++++++++ src/components/image/image_caption.styles.ts | 28 +++ src/components/image/image_caption.tsx | 41 ++++ .../image/image_fullscreen_wrapper.styles.ts | 95 +++++++++ .../image/image_fullscreen_wrapper.tsx | 97 ++++++++++ src/components/image/image_wrapper.styles.ts | 97 +--------- src/components/image/image_wrapper.tsx | 183 ++++++------------ 11 files changed, 538 insertions(+), 239 deletions(-) create mode 100644 src/components/image/image_button.styles.ts create mode 100644 src/components/image/image_button.tsx create mode 100644 src/components/image/image_caption.styles.ts create mode 100644 src/components/image/image_caption.tsx create mode 100644 src/components/image/image_fullscreen_wrapper.styles.ts create mode 100644 src/components/image/image_fullscreen_wrapper.tsx diff --git a/src/components/image/__snapshots__/image.test.tsx.snap b/src/components/image/__snapshots__/image.test.tsx.snap index 409b7f80442..68fe7a50786 100644 --- a/src/components/image/__snapshots__/image.test.tsx.snap +++ b/src/components/image/__snapshots__/image.test.tsx.snap @@ -11,6 +11,9 @@ exports[`EuiImage is rendered 1`] = ` data-test-subj="test subject string" src="/cat.jpg" /> +
`; @@ -20,7 +23,7 @@ exports[`EuiImage is rendered and allows fullscreen 1`] = ` > +
`; @@ -51,6 +57,9 @@ exports[`EuiImage is rendered with a float 1`] = ` float="left" src="/cat.jpg" /> +
`; @@ -64,6 +73,9 @@ exports[`EuiImage is rendered with a margin 1`] = ` margin="l" src="/cat.jpg" /> +
`; @@ -78,7 +90,7 @@ exports[`EuiImage is rendered with a node as the caption 1`] = ` src="/cat.jpg" />
caption @@ -97,6 +109,9 @@ exports[`EuiImage is rendered with custom size 1`] = ` src="/cat.jpg" style="max-width:50px;max-height:50px" /> +
`; @@ -110,6 +125,9 @@ exports[`EuiImage is rendered with src 1`] = ` float="left" src="/cat.jpg" /> +
`; @@ -125,7 +143,7 @@ exports[`EuiImage is rendered with wrapperProps 1`] = ` src="/cat.jpg" />
caption diff --git a/src/components/image/image.styles.ts b/src/components/image/image.styles.ts index 921efd2fd1a..d5ec4fbb96a 100644 --- a/src/components/image/image.styles.ts +++ b/src/components/image/image.styles.ts @@ -10,17 +10,25 @@ import { css } from '@emotion/react'; import { logicalCSS } from '../../global_styling'; import { UseEuiTheme } from '../../services'; import { euiShadow } from '../../themes/amsterdam/global_styling/mixins'; + export const euiImageStyles = (euiThemeContext: UseEuiTheme) => ({ - euiImage: css` + euiImage: css``, + // image modes + allowFullScreen: css` vertical-align: middle; max-width: 100%; &, - // Required for common usage of nesting within EuiText - [class*='euiText'] & { + // Required for common usage of nesting within EuiText + [class*='euiText'] & { ${logicalCSS('margin-bottom', 0)}; } `, + isFullScreen: css` + position: relative; + max-height: 80vh; + max-width: 80vw; + `, hasShadow: css` ${euiShadow(euiThemeContext, 's')}; `, @@ -51,9 +59,4 @@ export const euiImageStyles = (euiThemeContext: UseEuiTheme) => ({ // We set the width back to auto to ensure aspect ratio is kept width: auto; `, - fullScreen: css` - position: relative; - max-height: 80vh; - max-width: 80vw; - `, }); diff --git a/src/components/image/image.tsx b/src/components/image/image.tsx index b9e88a7536e..cfbdae4f84c 100644 --- a/src/components/image/image.tsx +++ b/src/components/image/image.tsx @@ -42,7 +42,10 @@ type ImageProps = CommonProps & Omit, 'src'>; export type EuiImageProps = ImageProps & - Omit; + Omit< + EuiImageWrapperProps, + 'isFullScreen' | 'setIsFullScreen' | 'allowFullScreenImg' + >; export const EuiImage: FunctionComponent = ({ className, @@ -65,14 +68,14 @@ export const EuiImage: FunctionComponent = ({ const styles = euiImageStyles(euiTheme); - const cssStyles = isFullScreen - ? [styles.fullScreen] - : [ - styles.euiImage, - isNamedSize && styles[size as EuiImageSize], - !isNamedSize && styles.customSize, - hasShadow && styles.hasShadow, - ]; + const cssStyles = [ + styles.euiImage, + isNamedSize && styles[size as EuiImageSize], + !isNamedSize && styles.customSize, + hasShadow && styles.hasShadow, + ]; + + const cssIsFullScreenStyles = [styles.euiImage, styles.isFullScreen]; const isCustomSize = !isFullScreen && !isNamedSize && size !== 'original'; @@ -94,6 +97,16 @@ export const EuiImage: FunctionComponent = ({ wrapperProps={wrapperProps} setIsFullScreen={setIsFullScreen} isFullScreen={isFullScreen} + fullScreenImg={ + {rest.alt} + } > { + const { euiTheme } = euiThemeContext; + + return { + // Base + euiImageButton: css` + position: relative; + cursor: pointer; + line-height: 0; + + &:hover, + &:focus { + ${hasShadow + ? `${euiShadow(euiThemeContext, 'm')};` + : `${euiShadow(euiThemeContext, 's')};`} + } + + ${euiCanAnimate} { + transition: box-shadow ${euiTheme.animation.fast} + ${euiTheme.animation.resistance}; + } + + &:focus { + ${euiFocusRing(euiTheme, 'outset')} + } + + &:hover [class*='euiImageButton__icon'] { + visibility: visible; + fill-opacity: 1; + } + `, + fullWidth: css` + width: 100%; + `, + }; +}; + +export const euiImageButtonIconStyles = ({ euiTheme }: UseEuiTheme) => ({ + // Base + euiImageButton__icon: css` + visibility: hidden; + fill-opacity: 0; + position: absolute; + ${logicalCSS('top', euiTheme.size.base)}; + ${logicalCSS('right', euiTheme.size.base)}; + cursor: pointer; + + ${euiCanAnimate} { + transition: fill-opacity ${euiTheme.animation.slow} + ${euiTheme.animation.resistance}; + } + `, +}); diff --git a/src/components/image/image_button.tsx b/src/components/image/image_button.tsx new file mode 100644 index 00000000000..a6010e55b68 --- /dev/null +++ b/src/components/image/image_button.tsx @@ -0,0 +1,91 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0 and the Server Side Public License, v 1; you may not use this file except + * in compliance with, at your election, the Elastic License 2.0 or the Server + * Side Public License, v 1. + */ + +import React, { FunctionComponent, HTMLAttributes, ReactNode } from 'react'; + +import { CommonProps } from '../common'; +import { useEuiTheme } from '../../services'; + +import { + euiImageButtonStyles, + euiImageButtonIconStyles, +} from './image_button.styles'; +import { EuiIcon } from '../icon'; + +export type EuiImageButtonIconColor = 'light' | 'dark'; + +const fullScreenIconColorMap: { + [color in EuiImageButtonIconColor]: string; +} = { + light: 'ghost', + dark: 'default', +}; + +export type EuiImageButtonProps = CommonProps & { + /** + * When set to `true` (default) will apply a slight shadow to the image + */ + hasShadow?: boolean; + /** + * Changes the color of the icon that floats above the image when it can be clicked to fullscreen. + * The default value of `light` is fine unless your image has a white background, in which case you should change it to `dark`. + */ + fullScreenIconColor?: EuiImageButtonIconColor; + /** + * Props to add to the wrapping `.euiImageFullscreenWrapper` figure + */ + wrapperProps?: HTMLAttributes; + captionNode?: ReactNode; + onClick: () => void; + onKeyDown?: (e: React.KeyboardEvent) => void; + isFullScreen: boolean; + isFullWidth: boolean; + allowFullScreen: boolean | undefined; +}; + +export const EuiImageButton: FunctionComponent = ({ + hasShadow, + children, + onClick, + onKeyDown, + isFullScreen, + isFullWidth, + allowFullScreen, + fullScreenIconColor = 'light', + ...rest +}) => { + const euiTheme = useEuiTheme(); + + const buttonStyles = euiImageButtonStyles(euiTheme, hasShadow); + + const cssButtonStyles = [ + buttonStyles.euiImageButton, + !isFullScreen && isFullWidth && buttonStyles.fullWidth, + ]; + + const iconStyles = euiImageButtonIconStyles(euiTheme); + const cssIconStyles = [iconStyles.euiImageButton__icon]; + + const iconColor = + fullScreenIconColorMap[fullScreenIconColor as EuiImageButtonIconColor]; + + return ( + + ); +}; diff --git a/src/components/image/image_caption.styles.ts b/src/components/image/image_caption.styles.ts new file mode 100644 index 00000000000..a274c967cf3 --- /dev/null +++ b/src/components/image/image_caption.styles.ts @@ -0,0 +1,28 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0 and the Server Side Public License, v 1; you may not use this file except + * in compliance with, at your election, the Elastic License 2.0 or the Server + * Side Public License, v 1. + */ + +import { css } from '@emotion/react'; +import { euiFontSize, logicalCSS } from '../../global_styling'; +import { UseEuiTheme, transparentize } from '../../services'; + +export const euiImageCaptionStyles = (euiThemeContext: UseEuiTheme) => { + const { euiTheme } = euiThemeContext; + + return { + // Base + euiImageCaption: css` + ${euiFontSize(euiThemeContext, 's')}; + ${logicalCSS('margin-top', euiTheme.size.xs)}; + text-align: center; + `, + isOnOverlayMask: css` + color: ${euiTheme.colors.ghost}; + text-shadow: 0 1px 2px ${transparentize(euiTheme.colors.ink, 0.6)}; + `, + }; +}; diff --git a/src/components/image/image_caption.tsx b/src/components/image/image_caption.tsx new file mode 100644 index 00000000000..72b3a590a2c --- /dev/null +++ b/src/components/image/image_caption.tsx @@ -0,0 +1,41 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0 and the Server Side Public License, v 1; you may not use this file except + * in compliance with, at your election, the Elastic License 2.0 or the Server + * Side Public License, v 1. + */ + +import React, { forwardRef, Ref, ReactNode } from 'react'; + +import { useEuiTheme } from '../../services'; +import { CommonProps } from '../common'; +import { euiImageCaptionStyles } from './image_caption.styles'; + +export type EuiImageCaptionProps = CommonProps & { + /** + * Provides the visible caption to the image + */ + caption?: ReactNode; + isOnOverlayMask?: boolean; + ref?: any; +}; + +export const EuiImageCaption = forwardRef( + ({ caption, isOnOverlayMask = false }, ref: Ref) => { + const euiTheme = useEuiTheme(); + const styles = euiImageCaptionStyles(euiTheme); + const cssstyles = [ + styles.euiImageCaption, + isOnOverlayMask && styles.isOnOverlayMask, + ]; + + return ( +
+ {caption} +
+ ); + } +); + +EuiImageCaption.displayName = 'EuiImageCaption'; diff --git a/src/components/image/image_fullscreen_wrapper.styles.ts b/src/components/image/image_fullscreen_wrapper.styles.ts new file mode 100644 index 00000000000..13deecf4012 --- /dev/null +++ b/src/components/image/image_fullscreen_wrapper.styles.ts @@ -0,0 +1,95 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0 and the Server Side Public License, v 1; you may not use this file except + * in compliance with, at your election, the Elastic License 2.0 or the Server + * Side Public License, v 1. + */ + +import { css, keyframes } from '@emotion/react'; +import { + euiFocusRing, + euiFontSize, + logicalCSS, + euiCanAnimate, +} from '../../global_styling'; +import { UseEuiTheme, transparentize } from '../../services'; +import { euiShadow } from '../../themes/amsterdam/global_styling/mixins'; + +export const euiImageFullscreenWrapperStyles = ( + euiThemeContext: UseEuiTheme +) => { + const { euiTheme } = euiThemeContext; + + return { + // Base + euiImageFullscreenWrapper: css` + display: inline-block; + max-width: 100%; + position: relative; + line-height: 0; // Fixes cropping when image is resized by forcing its height to be determined by the image not line-height + flex-shrink: 0; // Don't ever let this shrink in height if direct descendent of flex + position: relative; + max-height: 80vh; + max-width: 80vw; + + ${euiCanAnimate} { + animation: ${euiImageFullScreen(euiTheme.size.xxxxl)} + ${euiTheme.animation.extraSlow} ${euiTheme.animation.bounce}; + } + + &:hover [class*='euiImageFullscreenWrapper__caption'] { + text-decoration: underline; + } + `, + + // sizes + fullWidth: css` + width: 100%; + `, + }; +}; + +export const euiImageFullscreenWrapperCaptionStyles = ( + euiThemeContext: UseEuiTheme +) => { + const { euiTheme } = euiThemeContext; + + return { + // Base + euiImageFullscreenWrapper__caption: css` + ${euiFontSize(euiThemeContext, 's')}; + ${logicalCSS('margin-top', euiTheme.size.xs)}; + text-align: center; + `, + isFullScreen: css` + color: ${euiTheme.colors.ghost}; + text-shadow: 0 1px 2px ${transparentize(euiTheme.colors.ink, 0.6)}; + `, + }; +}; + +export const euiImageFullscreenWrapperFullScreenCloseIconStyles = ({ + euiTheme, +}: UseEuiTheme) => ({ + // Base + euiImageFullscreenWrapper__fullScreenCloseIcon: css` + position: absolute; + ${logicalCSS('top', euiTheme.size.base)}; + ${logicalCSS('right', euiTheme.size.base)}; + pointer-events: none; + fill: ${euiTheme.colors.ghost} !important; + `, +}); + +const euiImageFullScreen = (size: string) => keyframes` + 0% { + opacity: 0; + transform: translateY(${size}); + } + + 100% { + opacity: 1; + transform: translateY(0); + } +`; diff --git a/src/components/image/image_fullscreen_wrapper.tsx b/src/components/image/image_fullscreen_wrapper.tsx new file mode 100644 index 00000000000..5d5912aa5fa --- /dev/null +++ b/src/components/image/image_fullscreen_wrapper.tsx @@ -0,0 +1,97 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0 and the Server Side Public License, v 1; you may not use this file except + * in compliance with, at your election, the Elastic License 2.0 or the Server + * Side Public License, v 1. + */ + +import React, { FunctionComponent, HTMLAttributes, ReactNode } from 'react'; + +import { CommonProps } from '../common'; + +import { EuiIcon } from '../icon'; +import { EuiFocusTrap } from '../focus_trap'; +import { EuiOverlayMask } from '../overlay_mask'; +import { useEuiI18n } from '../i18n'; +import { useEuiTheme } from '../../services'; + +import { + euiImageFullscreenWrapperStyles, + euiImageFullscreenWrapperFullScreenCloseIconStyles, +} from './image_fullscreen_wrapper.styles'; + +export const SIZES = ['s', 'm', 'l', 'xl', 'fullWidth', 'original'] as const; +export type EuiImageFullscreenWrapperSize = typeof SIZES[number]; + +const FLOATS = ['left', 'right'] as const; +export type EuiImageFullscreenWrapperFloat = typeof FLOATS[number]; + +const MARGINS = ['s', 'm', 'l', 'xl'] as const; +export type EuiImageFullscreenWrapperMargin = typeof MARGINS[number]; + +export type EuiImageFullscreenWrapperFullScreenIconColor = 'light' | 'dark'; + +export type EuiImageFullscreenWrapperProps = CommonProps & { + /** + * Separate from the caption is a title on the alt tag itself. + * This one is required for accessibility. + */ + alt: string; + /** + * When set to `true` (default) will apply a slight shadow to the image + */ + hasShadow?: boolean; + /** + * Props to add to the wrapping `.euiImageFullscreenWrapper` figure + */ + wrapperProps?: HTMLAttributes; + captionNode?: ReactNode; + buttonNode?: ReactNode; + onClick: () => void; +}; + +export const EuiImageFullscreenWrapper: FunctionComponent = ({ + hasShadow, + alt, + wrapperProps, + onClick, + captionNode, + buttonNode, + ...rest +}) => { + const euiTheme = useEuiTheme(); + + const styles = euiImageFullscreenWrapperStyles(euiTheme); + + const cssStyles = [styles.euiImageFullscreenWrapper]; + + const fullScreenCloseIconStyles = euiImageFullscreenWrapperFullScreenCloseIconStyles( + euiTheme + ); + const cssFullScreenCloseIconStyles = [ + fullScreenCloseIconStyles.euiImageFullscreenWrapper__fullScreenCloseIcon, + ]; + + return ( + + + <> +
+ {buttonNode} + {captionNode} +
+ + +
+
+ ); +}; diff --git a/src/components/image/image_wrapper.styles.ts b/src/components/image/image_wrapper.styles.ts index 633d7d1b3fa..7dffb4cfd79 100644 --- a/src/components/image/image_wrapper.styles.ts +++ b/src/components/image/image_wrapper.styles.ts @@ -7,14 +7,8 @@ */ import { css, keyframes } from '@emotion/react'; -import { - euiFocusRing, - euiFontSize, - logicalCSS, - euiCanAnimate, -} from '../../global_styling'; -import { UseEuiTheme, transparentize } from '../../services'; -import { euiShadow } from '../../themes/amsterdam/global_styling/mixins'; +import { logicalCSS, euiCanAnimate } from '../../global_styling'; +import { UseEuiTheme } from '../../services'; const _imageMargins = ({ size, @@ -155,93 +149,6 @@ export const euiImageWrapperStyles = ( }; }; -export const euiImageWrapperButtonStyles = ( - euiThemeContext: UseEuiTheme, - hasShadow: boolean | undefined -) => { - const { euiTheme } = euiThemeContext; - - return { - // Base - euiImageWrapper__button: css` - position: relative; - cursor: pointer; - line-height: 0; - - &:hover, - &:focus { - ${hasShadow - ? `${euiShadow(euiThemeContext, 'm')};` - : `${euiShadow(euiThemeContext, 's')};`} - } - - ${euiCanAnimate} { - transition: box-shadow ${euiTheme.animation.fast} - ${euiTheme.animation.resistance}; - } - - &:focus { - ${euiFocusRing(euiTheme, 'outset')} - } - - &:hover [class*='euiImageWrapper__icon'] { - visibility: visible; - fill-opacity: 1; - } - `, - fullWidth: css` - width: 100%; - `, - }; -}; - -export const euiImageWrapperCaptionStyles = (euiThemeContext: UseEuiTheme) => { - const { euiTheme } = euiThemeContext; - - return { - // Base - euiImageWrapper__caption: css` - ${euiFontSize(euiThemeContext, 's')}; - ${logicalCSS('margin-top', euiTheme.size.xs)}; - text-align: center; - `, - isFullScreen: css` - color: ${euiTheme.colors.ghost}; - text-shadow: 0 1px 2px ${transparentize(euiTheme.colors.ink, 0.6)}; - `, - }; -}; - -export const euiImageWrapperIconStyles = ({ euiTheme }: UseEuiTheme) => ({ - // Base - euiImageWrapper__icon: css` - visibility: hidden; - fill-opacity: 0; - position: absolute; - ${logicalCSS('top', euiTheme.size.base)}; - ${logicalCSS('right', euiTheme.size.base)}; - cursor: pointer; - - ${euiCanAnimate} { - transition: fill-opacity ${euiTheme.animation.slow} - ${euiTheme.animation.resistance}; - } - `, -}); - -export const euiImageWrapperFullScreenCloseIconStyles = ({ - euiTheme, -}: UseEuiTheme) => ({ - // Base - euiImageWrapper__fullScreenCloseIcon: css` - position: absolute; - ${logicalCSS('top', euiTheme.size.base)}; - ${logicalCSS('right', euiTheme.size.base)}; - pointer-events: none; - fill: ${euiTheme.colors.ghost} !important; - `, -}); - const euiImageFullScreen = (size: string) => keyframes` 0% { opacity: 0; diff --git a/src/components/image/image_wrapper.tsx b/src/components/image/image_wrapper.tsx index fd2824069ed..6e809ad64c7 100644 --- a/src/components/image/image_wrapper.tsx +++ b/src/components/image/image_wrapper.tsx @@ -11,20 +11,16 @@ import classNames from 'classnames'; import { CommonProps } from '../common'; -import { EuiIcon } from '../../components/icon'; -import { EuiFocusTrap } from '../../components/focus_trap'; -import { EuiOverlayMask } from '../../components/overlay_mask'; import { useEuiI18n } from '../i18n'; import { keys, useEuiTheme, useIsWithinBreakpoints } from '../../services'; import { useInnerText } from '../inner_text'; -import { - euiImageWrapperStyles, - euiImageWrapperButtonStyles, - euiImageWrapperCaptionStyles, - euiImageWrapperIconStyles, - euiImageWrapperFullScreenCloseIconStyles, -} from './image_wrapper.styles'; +import { euiImageWrapperStyles } from './image_wrapper.styles'; + +import { EuiImageButton } from './image_button'; + +import { EuiImageCaption } from './image_caption'; +import { EuiImageFullscreenWrapper } from './image_fullscreen_wrapper'; export const SIZES = ['s', 'm', 'l', 'xl', 'fullWidth', 'original'] as const; export type EuiImageWrapperSize = typeof SIZES[number]; @@ -35,15 +31,6 @@ export type EuiImageWrapperFloat = typeof FLOATS[number]; const MARGINS = ['s', 'm', 'l', 'xl'] as const; export type EuiImageWrapperMargin = typeof MARGINS[number]; -export type EuiImageWrapperFullScreenIconColor = 'light' | 'dark'; - -const fullScreenIconColorMap: { - [color in EuiImageWrapperFullScreenIconColor]: string; -} = { - light: 'ghost', - dark: 'default', -}; - export type EuiImageWrapperProps = CommonProps & { /** * Separate from the caption is a title on the alt tag itself. @@ -56,11 +43,6 @@ export type EuiImageWrapperProps = CommonProps & { * `string` and `number` types will max both the width or height, whichever is greater. */ size?: EuiImageWrapperSize | number | string; - /** - * Changes the color of the icon that floats above the image when it can be clicked to fullscreen. - * The default value of `light` is fine unless your image has a white background, in which case you should change it to `dark`. - */ - fullScreenIconColor?: EuiImageWrapperFullScreenIconColor; /** * Provides the visible caption to the image */ @@ -85,6 +67,7 @@ export type EuiImageWrapperProps = CommonProps & { * Props to add to the wrapping `.euiImageWrapper` figure */ wrapperProps?: HTMLAttributes; + fullScreenImg?: ReactNode; isFullScreen: boolean; setIsFullScreen: (isFullScreen: boolean) => void; }; @@ -94,7 +77,6 @@ export const EuiImageWrapper: FunctionComponent = ({ caption, hasShadow, allowFullScreen, - fullScreenIconColor = 'light', alt, float, margin, @@ -102,6 +84,7 @@ export const EuiImageWrapper: FunctionComponent = ({ isFullScreen, setIsFullScreen, wrapperProps, + fullScreenImg, }) => { const isSmallScreen = useIsWithinBreakpoints(['xs', 's', 'm']); const hasFloatLeft = float === 'left'; @@ -144,100 +127,22 @@ export const EuiImageWrapper: FunctionComponent = ({ size === 'fullWidth' && styles.fullWidth, ]; - const cssFullScreenFigureStyles = [ - styles.euiImageWrapper, - isFullScreen && styles.isFullScreen, - ]; - - const buttonStyles = euiImageWrapperButtonStyles(euiTheme, hasShadow); - const cssButtonStyles = [ - buttonStyles.euiImageWrapper__button, - !isFullScreen && size === 'fullWidth' && buttonStyles.fullWidth, - ]; - - const captionStyles = euiImageWrapperCaptionStyles(euiTheme); - const cssCaptionStyles = [ - captionStyles.euiImageWrapper__caption, - isFullScreen && captionStyles.isFullScreen, - ]; - - const iconStyles = euiImageWrapperIconStyles(euiTheme); - const cssIconStyles = [iconStyles.euiImageWrapper__icon]; - - const fullScreenCloseIconStyles = euiImageWrapperFullScreenCloseIconStyles( - euiTheme - ); - const cssFullScreenCloseIconStyles = [ - fullScreenCloseIconStyles.euiImageWrapper__fullScreenCloseIcon, - ]; - const [optionalCaptionRef, optionalCaptionText] = useInnerText(); - let optionalCaption; - if (caption) { - optionalCaption = ( -
- {caption} -
- ); - } - - const allowFullScreenIcon = ( - - ); - const fullscreenLabel = useEuiI18n( + const openImageLabel = useEuiI18n( 'euiImageWrapper.openImage', 'Open fullscreen {alt} image', { alt } ); - const fullScreen = ( - - - <> -
- - {optionalCaption} -
- - -
-
+ const closeImageLabel = useEuiI18n( + 'euiImageWrapper.closeImage', + 'Close fullscreen {alt} image', + { alt } ); + const isFullWidth = size === 'fullWidth'; + return (
= ({ aria-label={optionalCaptionText} > {allowFullScreen ? ( - + <> + + {children} + + ) : ( - children + <>{children} )} - {isFullScreen && fullScreen} - {optionalCaption} + + + {isFullScreen && ( + + {fullScreenImg} + + } + captionNode={ + + } + /> + )}
); }; From bddf55f14f9198428e0610d6155f7d7f870ddf61 Mon Sep 17 00:00:00 2001 From: Elizabet Oliveira Date: Thu, 30 Jun 2022 19:02:41 +0100 Subject: [PATCH 15/48] Better components split --- .../image/__snapshots__/image.test.tsx.snap | 16 +- src/components/image/image.tsx | 95 ++++++------ src/components/image/image_button.tsx | 34 +---- src/components/image/image_caption.tsx | 14 +- .../image/image_fullscreen_wrapper.tsx | 98 +++++++----- src/components/image/image_types.ts | 126 ++++++++++++++++ src/components/image/image_wrapper.tsx | 142 +++--------------- 7 files changed, 269 insertions(+), 256 deletions(-) create mode 100644 src/components/image/image_types.ts diff --git a/src/components/image/__snapshots__/image.test.tsx.snap b/src/components/image/__snapshots__/image.test.tsx.snap index 68fe7a50786..37576ee0162 100644 --- a/src/components/image/__snapshots__/image.test.tsx.snap +++ b/src/components/image/__snapshots__/image.test.tsx.snap @@ -7,7 +7,7 @@ exports[`EuiImage is rendered 1`] = ` alt @@ -31,7 +31,7 @@ exports[`EuiImage is rendered and allows fullscreen 1`] = ` allowfullscreen="" alt="alt" aria-label="aria-label" - class="euiImage testClass1 testClass2 emotion-euiImage-l" + class="euiImage testClass1 testClass2 emotion-euiImage-allowFullScreen-l" data-test-subj="test subject string" src="/cat.jpg" /> @@ -53,7 +53,7 @@ exports[`EuiImage is rendered with a float 1`] = ` > alt @@ -69,7 +69,7 @@ exports[`EuiImage is rendered with a margin 1`] = ` > alt @@ -86,7 +86,7 @@ exports[`EuiImage is rendered with a node as the caption 1`] = ` alt
alt @@ -121,7 +121,7 @@ exports[`EuiImage is rendered with src 1`] = ` > alt @@ -139,7 +139,7 @@ exports[`EuiImage is rendered with wrapperProps 1`] = ` alt
; - -type ImageProps = CommonProps & - _EuiImageSrcOrUrl & - Omit, 'src'>; - -export type EuiImageProps = ImageProps & - Omit< - EuiImageWrapperProps, - 'isFullScreen' | 'setIsFullScreen' | 'allowFullScreenImg' - >; - export const EuiImage: FunctionComponent = ({ className, url, @@ -55,6 +38,7 @@ export const EuiImage: FunctionComponent = ({ hasShadow, style, wrapperProps, + fullScreenIconColor, ...rest }) => { const [isFullScreen, setIsFullScreen] = useState(false); @@ -70,6 +54,7 @@ export const EuiImage: FunctionComponent = ({ const cssStyles = [ styles.euiImage, + styles.allowFullScreen, isNamedSize && styles[size as EuiImageSize], !isNamedSize && styles.customSize, hasShadow && styles.hasShadow, @@ -89,33 +74,51 @@ export const EuiImage: FunctionComponent = ({ } : style; + const isFullWidth = size === 'fullWidth'; + return ( - + {rest.alt} - } - > - {rest.alt} - + + + {isFullScreen && ( + + {rest.alt} + + )} + ); }; diff --git a/src/components/image/image_button.tsx b/src/components/image/image_button.tsx index a6010e55b68..b9b5989d2ef 100644 --- a/src/components/image/image_button.tsx +++ b/src/components/image/image_button.tsx @@ -6,9 +6,7 @@ * Side Public License, v 1. */ -import React, { FunctionComponent, HTMLAttributes, ReactNode } from 'react'; - -import { CommonProps } from '../common'; +import React, { FunctionComponent } from 'react'; import { useEuiTheme } from '../../services'; import { @@ -16,8 +14,10 @@ import { euiImageButtonIconStyles, } from './image_button.styles'; import { EuiIcon } from '../icon'; - -export type EuiImageButtonIconColor = 'light' | 'dark'; +import type { + EuiImageButtonProps, + EuiImageButtonIconColor, +} from './image_types'; const fullScreenIconColorMap: { [color in EuiImageButtonIconColor]: string; @@ -26,28 +26,6 @@ const fullScreenIconColorMap: { dark: 'default', }; -export type EuiImageButtonProps = CommonProps & { - /** - * When set to `true` (default) will apply a slight shadow to the image - */ - hasShadow?: boolean; - /** - * Changes the color of the icon that floats above the image when it can be clicked to fullscreen. - * The default value of `light` is fine unless your image has a white background, in which case you should change it to `dark`. - */ - fullScreenIconColor?: EuiImageButtonIconColor; - /** - * Props to add to the wrapping `.euiImageFullscreenWrapper` figure - */ - wrapperProps?: HTMLAttributes; - captionNode?: ReactNode; - onClick: () => void; - onKeyDown?: (e: React.KeyboardEvent) => void; - isFullScreen: boolean; - isFullWidth: boolean; - allowFullScreen: boolean | undefined; -}; - export const EuiImageButton: FunctionComponent = ({ hasShadow, children, @@ -83,7 +61,7 @@ export const EuiImageButton: FunctionComponent = ({ {...rest} > {children} - {allowFullScreen && ( + {allowFullScreen && !isFullScreen && ( )} diff --git a/src/components/image/image_caption.tsx b/src/components/image/image_caption.tsx index 72b3a590a2c..44cbe8b8913 100644 --- a/src/components/image/image_caption.tsx +++ b/src/components/image/image_caption.tsx @@ -6,20 +6,12 @@ * Side Public License, v 1. */ -import React, { forwardRef, Ref, ReactNode } from 'react'; +import React, { forwardRef, Ref } from 'react'; import { useEuiTheme } from '../../services'; -import { CommonProps } from '../common'; -import { euiImageCaptionStyles } from './image_caption.styles'; -export type EuiImageCaptionProps = CommonProps & { - /** - * Provides the visible caption to the image - */ - caption?: ReactNode; - isOnOverlayMask?: boolean; - ref?: any; -}; +import { euiImageCaptionStyles } from './image_caption.styles'; +import type { EuiImageCaptionProps } from './image_types'; export const EuiImageCaption = forwardRef( ({ caption, isOnOverlayMask = false }, ref: Ref) => { diff --git a/src/components/image/image_fullscreen_wrapper.tsx b/src/components/image/image_fullscreen_wrapper.tsx index 5d5912aa5fa..25943e0be96 100644 --- a/src/components/image/image_fullscreen_wrapper.tsx +++ b/src/components/image/image_fullscreen_wrapper.tsx @@ -6,59 +6,36 @@ * Side Public License, v 1. */ -import React, { FunctionComponent, HTMLAttributes, ReactNode } from 'react'; - -import { CommonProps } from '../common'; +import React, { FunctionComponent } from 'react'; import { EuiIcon } from '../icon'; import { EuiFocusTrap } from '../focus_trap'; import { EuiOverlayMask } from '../overlay_mask'; import { useEuiI18n } from '../i18n'; -import { useEuiTheme } from '../../services'; +import { useEuiTheme, keys } from '../../services'; +import { useInnerText } from '../inner_text'; import { euiImageFullscreenWrapperStyles, euiImageFullscreenWrapperFullScreenCloseIconStyles, } from './image_fullscreen_wrapper.styles'; -export const SIZES = ['s', 'm', 'l', 'xl', 'fullWidth', 'original'] as const; -export type EuiImageFullscreenWrapperSize = typeof SIZES[number]; - -const FLOATS = ['left', 'right'] as const; -export type EuiImageFullscreenWrapperFloat = typeof FLOATS[number]; - -const MARGINS = ['s', 'm', 'l', 'xl'] as const; -export type EuiImageFullscreenWrapperMargin = typeof MARGINS[number]; +import type { EuiImageFullScreenWrapperProps } from './image_types'; -export type EuiImageFullscreenWrapperFullScreenIconColor = 'light' | 'dark'; - -export type EuiImageFullscreenWrapperProps = CommonProps & { - /** - * Separate from the caption is a title on the alt tag itself. - * This one is required for accessibility. - */ - alt: string; - /** - * When set to `true` (default) will apply a slight shadow to the image - */ - hasShadow?: boolean; - /** - * Props to add to the wrapping `.euiImageFullscreenWrapper` figure - */ - wrapperProps?: HTMLAttributes; - captionNode?: ReactNode; - buttonNode?: ReactNode; - onClick: () => void; -}; +import { EuiImageButton } from './image_button'; +import { EuiImageCaption } from './image_caption'; -export const EuiImageFullscreenWrapper: FunctionComponent = ({ +export const EuiImageFullScreenWrapper: FunctionComponent = ({ hasShadow, + caption, alt, + children, + isFullScreen, + setIsFullScreen, wrapperProps, - onClick, - captionNode, - buttonNode, - ...rest + allowFullScreen, + isFullWidth, + fullScreenIconColor, }) => { const euiTheme = useEuiTheme(); @@ -73,17 +50,56 @@ export const EuiImageFullscreenWrapper: FunctionComponent { + if (event.key === keys.ESCAPE) { + event.preventDefault(); + event.stopPropagation(); + closeFullScreen(); + } + }; + + const closeFullScreen = () => { + setIsFullScreen(false); + }; + + const closeImageLabel = useEuiI18n( + 'euiImageFullscreenWrapper.closeImage', + 'Close fullscreen {alt} image', + { alt } + ); + + const [optionalCaptionRef, optionalCaptionText] = useInnerText(); + return ( - + <>
- {buttonNode} - {captionNode} + + {children} + +
; + + setIsFullScreen: (isFullScreen: boolean) => void; +}; + +export type EuiImageAllowFullScreenProps = { + /** + * Accepts `s` / `m` / `l` / `xl` / `original` / `fullWidth` / or a CSS size of `number` or `string`. + * `fullWidth` will set the figure to stretch to 100% of its container. + * `string` and `number` types will max both the width or height, whichever is greater. + */ + size?: EuiImageWrapperSize | number | string; + /** + * When set to `true` will make the image clickable to a larger version + */ + allowFullScreen?: boolean; + /** + * Float the image to the left or right. Useful in large text blocks. + */ + float?: EuiImageWrapperFloat; + /** + * Margin around the image. + */ + margin?: EuiImageWrapperMargin; +}; + +export type _EuiImageSrcOrUrl = ExclusiveUnion< + { + /** + * Requires either `src` or `url` but defaults to using `src` if both are provided + */ + src: string; + }, + { + url: string; + } +>; + +export type EuiImageButtonProps = CommonProps & { + /** + * When set to `true` (default) will apply a slight shadow to the image + */ + hasShadow?: boolean; + /** + * Changes the color of the icon that floats above the image when it can be clicked to fullscreen. + * The default value of `light` is fine unless your image has a white background, in which case you should change it to `dark`. + */ + fullScreenIconColor?: EuiImageButtonIconColor; + onClick: () => void; + onKeyDown?: (e: React.KeyboardEvent) => void; + isFullScreen: boolean; + isFullWidth: boolean; + allowFullScreen: boolean | undefined; +}; + +export type EuiImageCaptionProps = { + /** + * Provides the visible caption to the image + */ + caption?: ReactNode; + isOnOverlayMask?: boolean; +}; + +export type ImageProps = CommonProps & + _EuiImageSrcOrUrl & + Omit, 'src'>; + +export type EuiImageProps = ImageProps & + EuiImageAllowFullScreenProps & + Omit & + Omit & + Omit< + EuiImageButtonProps, + 'onClick' | 'onKeyDown' | 'isFullScreen' | 'isFullWidth' | 'allowFullScreen' + >; + +export type EuiImageWrapperProps = EuiImageCommonWrapperProps & + EuiImageAllowFullScreenProps & + EuiImageButtonProps & + EuiImageCaptionProps; + +export type EuiImageFullScreenWrapperProps = EuiImageCommonWrapperProps & + EuiImageAllowFullScreenProps & + EuiImageButtonProps & + EuiImageCaptionProps; diff --git a/src/components/image/image_wrapper.tsx b/src/components/image/image_wrapper.tsx index 6e809ad64c7..53ed8235628 100644 --- a/src/components/image/image_wrapper.tsx +++ b/src/components/image/image_wrapper.tsx @@ -6,71 +6,19 @@ * Side Public License, v 1. */ -import React, { FunctionComponent, HTMLAttributes, ReactNode } from 'react'; +import React, { FunctionComponent } from 'react'; import classNames from 'classnames'; -import { CommonProps } from '../common'; - import { useEuiI18n } from '../i18n'; -import { keys, useEuiTheme, useIsWithinBreakpoints } from '../../services'; +import { useEuiTheme, useIsWithinBreakpoints } from '../../services'; import { useInnerText } from '../inner_text'; -import { euiImageWrapperStyles } from './image_wrapper.styles'; -import { EuiImageButton } from './image_button'; +import type { EuiImageWrapperProps } from './image_types'; +import { euiImageWrapperStyles } from './image_wrapper.styles'; +import { EuiImageButton } from './image_button'; import { EuiImageCaption } from './image_caption'; -import { EuiImageFullscreenWrapper } from './image_fullscreen_wrapper'; - -export const SIZES = ['s', 'm', 'l', 'xl', 'fullWidth', 'original'] as const; -export type EuiImageWrapperSize = typeof SIZES[number]; - -const FLOATS = ['left', 'right'] as const; -export type EuiImageWrapperFloat = typeof FLOATS[number]; - -const MARGINS = ['s', 'm', 'l', 'xl'] as const; -export type EuiImageWrapperMargin = typeof MARGINS[number]; - -export type EuiImageWrapperProps = CommonProps & { - /** - * Separate from the caption is a title on the alt tag itself. - * This one is required for accessibility. - */ - alt: string; - /** - * Accepts `s` / `m` / `l` / `xl` / `original` / `fullWidth` / or a CSS size of `number` or `string`. - * `fullWidth` will set the figure to stretch to 100% of its container. - * `string` and `number` types will max both the width or height, whichever is greater. - */ - size?: EuiImageWrapperSize | number | string; - /** - * Provides the visible caption to the image - */ - caption?: ReactNode; - /** - * When set to `true` (default) will apply a slight shadow to the image - */ - hasShadow?: boolean; - /** - * When set to `true` will make the image clickable to a larger version - */ - allowFullScreen?: boolean; - /** - * Float the image to the left or right. Useful in large text blocks. - */ - float?: EuiImageWrapperFloat; - /** - * Margin around the image. - */ - margin?: EuiImageWrapperMargin; - /** - * Props to add to the wrapping `.euiImageWrapper` figure - */ - wrapperProps?: HTMLAttributes; - fullScreenImg?: ReactNode; - isFullScreen: boolean; - setIsFullScreen: (isFullScreen: boolean) => void; -}; export const EuiImageWrapper: FunctionComponent = ({ size = 'original', @@ -84,24 +32,13 @@ export const EuiImageWrapper: FunctionComponent = ({ isFullScreen, setIsFullScreen, wrapperProps, - fullScreenImg, + fullScreenIconColor, + isFullWidth, }) => { const isSmallScreen = useIsWithinBreakpoints(['xs', 's', 'm']); const hasFloatLeft = float === 'left'; const hasFloatRight = float === 'right'; - const onKeyDown = (event: React.KeyboardEvent) => { - if (event.key === keys.ESCAPE) { - event.preventDefault(); - event.stopPropagation(); - closeFullScreen(); - } - }; - - const closeFullScreen = () => { - setIsFullScreen(false); - }; - const openFullScreen = () => { setIsFullScreen(true); }; @@ -135,14 +72,6 @@ export const EuiImageWrapper: FunctionComponent = ({ { alt } ); - const closeImageLabel = useEuiI18n( - 'euiImageWrapper.closeImage', - 'Close fullscreen {alt} image', - { alt } - ); - - const isFullWidth = size === 'fullWidth'; - return (
= ({ aria-label={optionalCaptionText} > {allowFullScreen ? ( - <> - - {children} - - + + {children} + ) : ( - <>{children} + children )} - - {isFullScreen && ( - - {fullScreenImg} - - } - captionNode={ - - } - /> - )}
); }; From d85a6831f0f028f7235ef6f9bb60428f1a410738 Mon Sep 17 00:00:00 2001 From: Elizabet Oliveira Date: Thu, 30 Jun 2022 19:25:57 +0100 Subject: [PATCH 16/48] Fixing caption hover styles --- src/components/image/image.tsx | 5 ++- .../image/image_fullscreen_wrapper.styles.ts | 31 ++----------------- src/components/image/image_wrapper.styles.ts | 16 +--------- 3 files changed, 8 insertions(+), 44 deletions(-) diff --git a/src/components/image/image.tsx b/src/components/image/image.tsx index 03bccfd65dd..5193e43a5f3 100644 --- a/src/components/image/image.tsx +++ b/src/components/image/image.tsx @@ -39,6 +39,7 @@ export const EuiImage: FunctionComponent = ({ style, wrapperProps, fullScreenIconColor, + allowFullScreen, ...rest }) => { const [isFullScreen, setIsFullScreen] = useState(false); @@ -88,6 +89,7 @@ export const EuiImage: FunctionComponent = ({ setIsFullScreen={setIsFullScreen} isFullWidth={isFullWidth} fullScreenIconColor={fullScreenIconColor} + allowFullScreen={allowFullScreen} > = ({ /> - {isFullScreen && ( + {allowFullScreen && isFullScreen && ( = ({ setIsFullScreen={setIsFullScreen} fullScreenIconColor={fullScreenIconColor} isFullWidth={isFullWidth} + allowFullScreen={allowFullScreen} > { - const { euiTheme } = euiThemeContext; - - return { - // Base - euiImageFullscreenWrapper__caption: css` - ${euiFontSize(euiThemeContext, 's')}; - ${logicalCSS('margin-top', euiTheme.size.xs)}; - text-align: center; - `, - isFullScreen: css` - color: ${euiTheme.colors.ghost}; - text-shadow: 0 1px 2px ${transparentize(euiTheme.colors.ink, 0.6)}; - `, - }; -}; - export const euiImageFullscreenWrapperFullScreenCloseIconStyles = ({ euiTheme, }: UseEuiTheme) => ({ diff --git a/src/components/image/image_wrapper.styles.ts b/src/components/image/image_wrapper.styles.ts index 7dffb4cfd79..d94ec10d820 100644 --- a/src/components/image/image_wrapper.styles.ts +++ b/src/components/image/image_wrapper.styles.ts @@ -72,21 +72,7 @@ export const euiImageWrapperStyles = ( flex-shrink: 0; // Don't ever let this shrink in height if direct descendent of flex `, allowFullScreen: css` - &:hover [class*='euiImageWrapper__caption'] { - text-decoration: underline; - } - `, - isFullScreen: css` - position: relative; - max-height: 80vh; - max-width: 80vw; - - ${euiCanAnimate} { - animation: ${euiImageFullScreen(euiTheme.size.xxxxl)} - ${euiTheme.animation.extraSlow} ${euiTheme.animation.bounce}; - } - - &:hover [class*='euiImageWrapper__caption'] { + &:hover [class*='euiImageCaption'] { text-decoration: underline; } `, From c4de638fb254dc5f1e5a4969b7421e807d7236a5 Mon Sep 17 00:00:00 2001 From: Chandler Prall Date: Thu, 30 Jun 2022 13:27:10 -0600 Subject: [PATCH 17/48] Omit onClick from EuiImageWrapperProps and EuiImageFullScreenWrapperProps --- src/components/image/image_types.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/components/image/image_types.ts b/src/components/image/image_types.ts index 621843fd2e1..d203cc3cd42 100644 --- a/src/components/image/image_types.ts +++ b/src/components/image/image_types.ts @@ -117,10 +117,10 @@ export type EuiImageProps = ImageProps & export type EuiImageWrapperProps = EuiImageCommonWrapperProps & EuiImageAllowFullScreenProps & - EuiImageButtonProps & + Omit & EuiImageCaptionProps; export type EuiImageFullScreenWrapperProps = EuiImageCommonWrapperProps & EuiImageAllowFullScreenProps & - EuiImageButtonProps & + Omit & EuiImageCaptionProps; From 0bf23ee32c5dc8ff3763799eabcc7042a14afff6 Mon Sep 17 00:00:00 2001 From: Elizabet Oliveira Date: Thu, 30 Jun 2022 21:21:31 +0100 Subject: [PATCH 18/48] Reorganizing imports and types --- .../image/__snapshots__/image.test.tsx.snap | 1 - src/components/image/image.tsx | 11 ++--------- src/components/image/image_types.ts | 17 +++++++++-------- src/components/image/image_wrapper.styles.ts | 16 ++-------------- src/components/image/image_wrapper.tsx | 3 +-- src/components/image/index.ts | 2 +- 6 files changed, 15 insertions(+), 35 deletions(-) diff --git a/src/components/image/__snapshots__/image.test.tsx.snap b/src/components/image/__snapshots__/image.test.tsx.snap index 37576ee0162..7120c7e5330 100644 --- a/src/components/image/__snapshots__/image.test.tsx.snap +++ b/src/components/image/__snapshots__/image.test.tsx.snap @@ -28,7 +28,6 @@ exports[`EuiImage is rendered and allows fullscreen 1`] = ` type="button" > alt = ({ className, @@ -83,7 +77,6 @@ export const EuiImage: FunctionComponent = ({ {...(rest as EuiImageWrapperProps)} alt={rest.alt} hasShadow={hasShadow} - size={size} wrapperProps={wrapperProps} isFullScreen={isFullScreen} setIsFullScreen={setIsFullScreen} diff --git a/src/components/image/image_types.ts b/src/components/image/image_types.ts index d203cc3cd42..9fe975180c6 100644 --- a/src/components/image/image_types.ts +++ b/src/components/image/image_types.ts @@ -10,7 +10,7 @@ import { HTMLAttributes, ReactNode, ImgHTMLAttributes } from 'react'; import { CommonProps, ExclusiveUnion } from '../common'; export const SIZES = ['s', 'm', 'l', 'xl', 'fullWidth', 'original'] as const; -export type EuiImageWrapperSize = typeof SIZES[number]; +export type EuiImageSize = typeof SIZES[number]; const FLOATS = ['left', 'right'] as const; export type EuiImageWrapperFloat = typeof FLOATS[number]; @@ -45,12 +45,6 @@ export type EuiImageCommonWrapperProps = CommonProps & { }; export type EuiImageAllowFullScreenProps = { - /** - * Accepts `s` / `m` / `l` / `xl` / `original` / `fullWidth` / or a CSS size of `number` or `string`. - * `fullWidth` will set the figure to stretch to 100% of its container. - * `string` and `number` types will max both the width or height, whichever is greater. - */ - size?: EuiImageWrapperSize | number | string; /** * When set to `true` will make the image clickable to a larger version */ @@ -113,7 +107,14 @@ export type EuiImageProps = ImageProps & Omit< EuiImageButtonProps, 'onClick' | 'onKeyDown' | 'isFullScreen' | 'isFullWidth' | 'allowFullScreen' - >; + > & { + /** + * Accepts `s` / `m` / `l` / `xl` / `original` / `fullWidth` / or a CSS size of `number` or `string`. + * `fullWidth` will set the figure to stretch to 100% of its container. + * `string` and `number` types will max both the width or height, whichever is greater. + */ + size?: EuiImageSize | number | string; + }; export type EuiImageWrapperProps = EuiImageCommonWrapperProps & EuiImageAllowFullScreenProps & diff --git a/src/components/image/image_wrapper.styles.ts b/src/components/image/image_wrapper.styles.ts index d94ec10d820..35b02920f3f 100644 --- a/src/components/image/image_wrapper.styles.ts +++ b/src/components/image/image_wrapper.styles.ts @@ -6,8 +6,8 @@ * Side Public License, v 1. */ -import { css, keyframes } from '@emotion/react'; -import { logicalCSS, euiCanAnimate } from '../../global_styling'; +import { css } from '@emotion/react'; +import { logicalCSS } from '../../global_styling'; import { UseEuiTheme } from '../../services'; const _imageMargins = ({ @@ -134,15 +134,3 @@ export const euiImageWrapperStyles = ( `, }; }; - -const euiImageFullScreen = (size: string) => keyframes` - 0% { - opacity: 0; - transform: translateY(${size}); - } - - 100% { - opacity: 1; - transform: translateY(0); - } -`; diff --git a/src/components/image/image_wrapper.tsx b/src/components/image/image_wrapper.tsx index 53ed8235628..8d2dbb557a5 100644 --- a/src/components/image/image_wrapper.tsx +++ b/src/components/image/image_wrapper.tsx @@ -21,7 +21,6 @@ import { EuiImageButton } from './image_button'; import { EuiImageCaption } from './image_caption'; export const EuiImageWrapper: FunctionComponent = ({ - size = 'original', caption, hasShadow, allowFullScreen, @@ -61,7 +60,7 @@ export const EuiImageWrapper: FunctionComponent = ({ float && styles[float], margin && styles[margin], allowFullScreen && styles.allowFullScreen, - size === 'fullWidth' && styles.fullWidth, + isFullWidth && styles.fullWidth, ]; const [optionalCaptionRef, optionalCaptionText] = useInnerText(); diff --git a/src/components/image/index.ts b/src/components/image/index.ts index fdbb7b61bcd..6fc04e46d62 100644 --- a/src/components/image/index.ts +++ b/src/components/image/index.ts @@ -6,5 +6,5 @@ * Side Public License, v 1. */ -export type { EuiImageProps } from './image'; +export type { EuiImageProps } from './image_types'; export { EuiImage } from './image'; From 3dcf2626ab50456f39e258e157f5332d092a18fb Mon Sep 17 00:00:00 2001 From: Elizabet Oliveira Date: Mon, 4 Jul 2022 15:17:23 +0100 Subject: [PATCH 19/48] Better styles --- .../image/__snapshots__/image.test.tsx.snap | 16 ++++++++-------- src/components/image/image.styles.ts | 9 ++++----- src/components/image/image.tsx | 1 - .../image/image_fullscreen_wrapper.styles.ts | 3 +-- src/components/image/image_wrapper.styles.ts | 6 +++--- 5 files changed, 16 insertions(+), 19 deletions(-) diff --git a/src/components/image/__snapshots__/image.test.tsx.snap b/src/components/image/__snapshots__/image.test.tsx.snap index 7120c7e5330..724e8038e6a 100644 --- a/src/components/image/__snapshots__/image.test.tsx.snap +++ b/src/components/image/__snapshots__/image.test.tsx.snap @@ -7,7 +7,7 @@ exports[`EuiImage is rendered 1`] = ` alt @@ -30,7 +30,7 @@ exports[`EuiImage is rendered and allows fullscreen 1`] = ` alt @@ -52,7 +52,7 @@ exports[`EuiImage is rendered with a float 1`] = ` > alt @@ -68,7 +68,7 @@ exports[`EuiImage is rendered with a margin 1`] = ` > alt @@ -85,7 +85,7 @@ exports[`EuiImage is rendered with a node as the caption 1`] = ` alt
alt @@ -120,7 +120,7 @@ exports[`EuiImage is rendered with src 1`] = ` > alt @@ -138,7 +138,7 @@ exports[`EuiImage is rendered with wrapperProps 1`] = ` alt
({ - euiImage: css``, - // image modes - allowFullScreen: css` + euiImage: css` vertical-align: middle; max-width: 100%; &, - // Required for common usage of nesting within EuiText - [class*='euiText'] & { + // Required for common usage of nesting within EuiText + [class*='euiText'] & { ${logicalCSS('margin-bottom', 0)}; } `, + // Variations isFullScreen: css` position: relative; max-height: 80vh; diff --git a/src/components/image/image.tsx b/src/components/image/image.tsx index 4cda625e919..848c390c7b7 100644 --- a/src/components/image/image.tsx +++ b/src/components/image/image.tsx @@ -49,7 +49,6 @@ export const EuiImage: FunctionComponent = ({ const cssStyles = [ styles.euiImage, - styles.allowFullScreen, isNamedSize && styles[size as EuiImageSize], !isNamedSize && styles.customSize, hasShadow && styles.hasShadow, diff --git a/src/components/image/image_fullscreen_wrapper.styles.ts b/src/components/image/image_fullscreen_wrapper.styles.ts index 518e01172f5..8aef9c563c3 100644 --- a/src/components/image/image_fullscreen_wrapper.styles.ts +++ b/src/components/image/image_fullscreen_wrapper.styles.ts @@ -36,8 +36,7 @@ export const euiImageFullscreenWrapperStyles = ( text-decoration: underline; } `, - - // sizes + // Sizes fullWidth: css` width: 100%; `, diff --git a/src/components/image/image_wrapper.styles.ts b/src/components/image/image_wrapper.styles.ts index 35b02920f3f..e9bd61b728f 100644 --- a/src/components/image/image_wrapper.styles.ts +++ b/src/components/image/image_wrapper.styles.ts @@ -76,7 +76,7 @@ export const euiImageWrapperStyles = ( text-decoration: underline; } `, - // margins + // Margins s: css( _imageMargins({ size: euiTheme.size.s, @@ -109,7 +109,7 @@ export const euiImageWrapperStyles = ( isSmallScreen, }) ), - // floats + // Floats left: css` ${isSmallScreen ? ` @@ -128,7 +128,7 @@ export const euiImageWrapperStyles = ( float: right; `} `, - // sizes + // Sizes fullWidth: css` width: 100%; `, From 4d9b5467fa861a7687d7fba9355f254e6e9a3fbe Mon Sep 17 00:00:00 2001 From: Elizabet Oliveira Date: Mon, 4 Jul 2022 16:52:31 +0100 Subject: [PATCH 20/48] Adding `commonImgProps` and `commonWrapperProps` --- src/components/image/image.tsx | 54 +++++++++++++++------------------- 1 file changed, 23 insertions(+), 31 deletions(-) diff --git a/src/components/image/image.tsx b/src/components/image/image.tsx index 848c390c7b7..6f967b08f26 100644 --- a/src/components/image/image.tsx +++ b/src/components/image/image.tsx @@ -26,6 +26,7 @@ import { SIZES } from './image_types'; export const EuiImage: FunctionComponent = ({ className, + alt, url, src, size = 'original', @@ -70,48 +71,39 @@ export const EuiImage: FunctionComponent = ({ const isFullWidth = size === 'fullWidth'; + const commonWrapperProps = { + alt: alt, + hasShadow: hasShadow, + wrapperProps: wrapperProps, + isFullScreen: isFullScreen, + setIsFullScreen: setIsFullScreen, + fullScreenIconColor: fullScreenIconColor, + isFullWidth: isFullWidth, + allowFullScreen: allowFullScreen, + }; + + const commonImgProps = { + className: classes, + style: imageStyle, + src: src || url, + ...rest, + } as ImageProps; + return ( <> - {rest.alt} + {alt} {allowFullScreen && isFullScreen && ( - {rest.alt} + {alt} )} From 51fa9e6beeaa803bbf5610a115630edde7f0807f Mon Sep 17 00:00:00 2001 From: Elizabet Oliveira Date: Tue, 5 Jul 2022 15:20:27 +0100 Subject: [PATCH 21/48] CL --- src/components/image/image_types.ts | 5 +---- upcoming_changelogs/5969.md | 1 + 2 files changed, 2 insertions(+), 4 deletions(-) diff --git a/src/components/image/image_types.ts b/src/components/image/image_types.ts index 9fe975180c6..9e0f62a12fc 100644 --- a/src/components/image/image_types.ts +++ b/src/components/image/image_types.ts @@ -26,7 +26,6 @@ export type EuiImageCommonWrapperProps = CommonProps & { * This one is required for accessibility. */ alt: string; - /** * When set to `true` (default) will apply a slight shadow to the image */ @@ -35,12 +34,10 @@ export type EuiImageCommonWrapperProps = CommonProps & { * When set to `true` will make the image clickable to a larger version */ allowFullScreen?: boolean; - /** * Props to add to the wrapping figure element */ wrapperProps?: HTMLAttributes; - setIsFullScreen: (isFullScreen: boolean) => void; }; @@ -100,7 +97,7 @@ export type ImageProps = CommonProps & _EuiImageSrcOrUrl & Omit, 'src'>; -export type EuiImageProps = ImageProps & +export type EuiImageProps = Omit & EuiImageAllowFullScreenProps & Omit & Omit & diff --git a/upcoming_changelogs/5969.md b/upcoming_changelogs/5969.md index 476fdb1406c..0714587ae59 100644 --- a/upcoming_changelogs/5969.md +++ b/upcoming_changelogs/5969.md @@ -1,4 +1,5 @@ - Updated `EuiText.img` styles to prevent images from growing full width +- Added `wrapperProps` prop to `EuiImage` **CSS-in-JS** From e52dad8dc97d1cb9615bfa0843ee4bcf5f9c1743 Mon Sep 17 00:00:00 2001 From: Elizabet Oliveira Date: Tue, 5 Jul 2022 15:33:35 +0100 Subject: [PATCH 22/48] CL again --- upcoming_changelogs/5969.md | 1 + 1 file changed, 1 insertion(+) diff --git a/upcoming_changelogs/5969.md b/upcoming_changelogs/5969.md index 0714587ae59..e3cbe59ab2c 100644 --- a/upcoming_changelogs/5969.md +++ b/upcoming_changelogs/5969.md @@ -1,5 +1,6 @@ - Updated `EuiText.img` styles to prevent images from growing full width - Added `wrapperProps` prop to `EuiImage` +- Updated `EuiImage`'s full screen mode to use the `fullScreenExit` icon **CSS-in-JS** From 62dbd451eca8c3980d54633b1b9391c246a6e154 Mon Sep 17 00:00:00 2001 From: Elizabet Oliveira Date: Tue, 5 Jul 2022 16:31:50 +0100 Subject: [PATCH 23/48] Adding `euiImageFullScreenWrapper` className --- src/components/image/image_fullscreen_wrapper.tsx | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/src/components/image/image_fullscreen_wrapper.tsx b/src/components/image/image_fullscreen_wrapper.tsx index 25943e0be96..89be0f41b55 100644 --- a/src/components/image/image_fullscreen_wrapper.tsx +++ b/src/components/image/image_fullscreen_wrapper.tsx @@ -7,6 +7,7 @@ */ import React, { FunctionComponent } from 'react'; +import classNames from 'classnames'; import { EuiIcon } from '../icon'; import { EuiFocusTrap } from '../focus_trap'; @@ -50,6 +51,11 @@ export const EuiImageFullScreenWrapper: FunctionComponent { if (event.key === keys.ESCAPE) { event.preventDefault(); @@ -79,6 +85,7 @@ export const EuiImageFullScreenWrapper: FunctionComponent
From 2ee3c5069bc3b0600ce8d7db2b23498b0a18bd1c Mon Sep 17 00:00:00 2001 From: Elizabet Oliveira Date: Tue, 5 Jul 2022 17:24:30 +0100 Subject: [PATCH 24/48] Adding `Breaking changes` CL entry --- upcoming_changelogs/5969.md | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/upcoming_changelogs/5969.md b/upcoming_changelogs/5969.md index e3cbe59ab2c..f47e77683fa 100644 --- a/upcoming_changelogs/5969.md +++ b/upcoming_changelogs/5969.md @@ -1,7 +1,10 @@ - Updated `EuiText.img` styles to prevent images from growing full width -- Added `wrapperProps` prop to `EuiImage` - Updated `EuiImage`'s full screen mode to use the `fullScreenExit` icon **CSS-in-JS** -- Converted `EuiImage` to Emotion \ No newline at end of file +- Converted `EuiImage` to Emotion + +**Breaking changes** + +- Updated `EuiImage.className` to be applied to the `img` instead of the parent wrapper `figure` and added `wrapperProps` prop so that consumers can apply props to the `figure` element From 6a6cf5cb9992c61e76b8d43af0de73583b51d1fd Mon Sep 17 00:00:00 2001 From: Elizabet Oliveira Date: Mon, 11 Jul 2022 17:23:00 +0100 Subject: [PATCH 25/48] Adding `logicalTextAlignCSS` --- src/components/image/image_caption.styles.ts | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/src/components/image/image_caption.styles.ts b/src/components/image/image_caption.styles.ts index a274c967cf3..dce19611733 100644 --- a/src/components/image/image_caption.styles.ts +++ b/src/components/image/image_caption.styles.ts @@ -7,7 +7,11 @@ */ import { css } from '@emotion/react'; -import { euiFontSize, logicalCSS } from '../../global_styling'; +import { + euiFontSize, + logicalCSS, + logicalTextAlignCSS, +} from '../../global_styling'; import { UseEuiTheme, transparentize } from '../../services'; export const euiImageCaptionStyles = (euiThemeContext: UseEuiTheme) => { @@ -18,7 +22,7 @@ export const euiImageCaptionStyles = (euiThemeContext: UseEuiTheme) => { euiImageCaption: css` ${euiFontSize(euiThemeContext, 's')}; ${logicalCSS('margin-top', euiTheme.size.xs)}; - text-align: center; + ${logicalTextAlignCSS('center')}; `, isOnOverlayMask: css` color: ${euiTheme.colors.ghost}; From 18f6555cdc13a9b29ae257f229b7324257d74fa6 Mon Sep 17 00:00:00 2001 From: Elizabet Oliveira Date: Mon, 11 Jul 2022 17:44:07 +0100 Subject: [PATCH 26/48] More `logicalCSS` --- src/components/image/image.styles.ts | 6 +++--- src/components/image/image_fullscreen_wrapper.styles.ts | 5 ++--- 2 files changed, 5 insertions(+), 6 deletions(-) diff --git a/src/components/image/image.styles.ts b/src/components/image/image.styles.ts index 56da931c18b..73eb7808ae4 100644 --- a/src/components/image/image.styles.ts +++ b/src/components/image/image.styles.ts @@ -14,7 +14,7 @@ import { euiShadow } from '../../themes/amsterdam/global_styling/mixins'; export const euiImageStyles = (euiThemeContext: UseEuiTheme) => ({ euiImage: css` vertical-align: middle; - max-width: 100%; + ${logicalCSS('max-width', '100%')}; &, // Required for common usage of nesting within EuiText @@ -25,8 +25,8 @@ export const euiImageStyles = (euiThemeContext: UseEuiTheme) => ({ // Variations isFullScreen: css` position: relative; - max-height: 80vh; - max-width: 80vw; + ${logicalCSS('max-height', '80vh')}; + ${logicalCSS('max-width', '80vh')}; `, hasShadow: css` ${euiShadow(euiThemeContext, 's')}; diff --git a/src/components/image/image_fullscreen_wrapper.styles.ts b/src/components/image/image_fullscreen_wrapper.styles.ts index 8aef9c563c3..88e5d8b5664 100644 --- a/src/components/image/image_fullscreen_wrapper.styles.ts +++ b/src/components/image/image_fullscreen_wrapper.styles.ts @@ -19,13 +19,12 @@ export const euiImageFullscreenWrapperStyles = ( // Base euiImageFullscreenWrapper: css` display: inline-block; - max-width: 100%; position: relative; line-height: 0; // Fixes cropping when image is resized by forcing its height to be determined by the image not line-height flex-shrink: 0; // Don't ever let this shrink in height if direct descendent of flex position: relative; - max-height: 80vh; - max-width: 80vw; + ${logicalCSS('max-height', '80vh')}; + ${logicalCSS('max-width', '80vh')}; ${euiCanAnimate} { animation: ${euiImageFullScreen(euiTheme.size.xxxxl)} From 74207d01c87f0fb9666068f95f8824ff924f1ad2 Mon Sep 17 00:00:00 2001 From: Elizabet Oliveira Date: Tue, 12 Jul 2022 16:52:09 +0100 Subject: [PATCH 27/48] Removing `useIsWithinBreakpoints` and using a media query instead --- src/components/image/image_wrapper.styles.ts | 56 +++++++++----------- src/components/image/image_wrapper.tsx | 10 +--- 2 files changed, 27 insertions(+), 39 deletions(-) diff --git a/src/components/image/image_wrapper.styles.ts b/src/components/image/image_wrapper.styles.ts index e9bd61b728f..1fc3566ec6e 100644 --- a/src/components/image/image_wrapper.styles.ts +++ b/src/components/image/image_wrapper.styles.ts @@ -14,28 +14,27 @@ const _imageMargins = ({ size, hasFloatLeft, hasFloatRight, - isSmallScreen, + euiTheme, }: { size: string; hasFloatLeft: boolean | undefined; hasFloatRight: boolean | undefined; - isSmallScreen: boolean; + euiTheme: UseEuiTheme['euiTheme']; }) => { const hasFloat = hasFloatLeft || hasFloatRight; - let mainStyles; + const mainStyles = ` + ${logicalCSS('margin-horizontal', size)}; + ${logicalCSS('margin-vertical', size)}; - if (hasFloat && isSmallScreen) { - mainStyles = ` + ${ + hasFloat && + `@media only screen and (max-width: ${euiTheme.breakpoint.m}px) { ${logicalCSS('margin-horizontal', 'inherit')}; ${logicalCSS('margin-vertical', 'inherit')}; - `; - } else { - mainStyles = ` - ${logicalCSS('margin-horizontal', size)}; - ${logicalCSS('margin-vertical', size)}; - `; + }` } +`; const floatLeftStyles = ` ${logicalCSS('margin-left', '0')}; @@ -57,8 +56,7 @@ const _imageMargins = ({ export const euiImageWrapperStyles = ( euiThemeContext: UseEuiTheme, hasFloatLeft: boolean | undefined, - hasFloatRight: boolean | undefined, - isSmallScreen: boolean + hasFloatRight: boolean | undefined ) => { const { euiTheme } = euiThemeContext; @@ -82,7 +80,7 @@ export const euiImageWrapperStyles = ( size: euiTheme.size.s, hasFloatLeft, hasFloatRight, - isSmallScreen, + euiTheme, }) ), m: css( @@ -90,7 +88,7 @@ export const euiImageWrapperStyles = ( size: euiTheme.size.base, hasFloatLeft, hasFloatRight, - isSmallScreen, + euiTheme, }) ), l: css( @@ -98,7 +96,7 @@ export const euiImageWrapperStyles = ( size: euiTheme.size.l, hasFloatLeft, hasFloatRight, - isSmallScreen, + euiTheme, }) ), xl: css( @@ -106,27 +104,23 @@ export const euiImageWrapperStyles = ( size: euiTheme.size.xl, hasFloatLeft, hasFloatRight, - isSmallScreen, + euiTheme, }) ), // Floats left: css` - ${isSmallScreen - ? ` - float: none; - ` - : ` - float: left; - `} + float: left; + + @media only screen and (max-width: ${euiTheme.breakpoint.m}px) { + float: none; + } `, right: css` - ${isSmallScreen - ? ` - float: none; - ` - : ` - float: right; - `} + float: right; + + @media only screen and (max-width: ${euiTheme.breakpoint.m}px) { + float: none; + } `, // Sizes fullWidth: css` diff --git a/src/components/image/image_wrapper.tsx b/src/components/image/image_wrapper.tsx index 8d2dbb557a5..628ea94de97 100644 --- a/src/components/image/image_wrapper.tsx +++ b/src/components/image/image_wrapper.tsx @@ -11,7 +11,7 @@ import classNames from 'classnames'; import { useEuiI18n } from '../i18n'; -import { useEuiTheme, useIsWithinBreakpoints } from '../../services'; +import { useEuiTheme } from '../../services'; import { useInnerText } from '../inner_text'; import type { EuiImageWrapperProps } from './image_types'; @@ -34,7 +34,6 @@ export const EuiImageWrapper: FunctionComponent = ({ fullScreenIconColor, isFullWidth, }) => { - const isSmallScreen = useIsWithinBreakpoints(['xs', 's', 'm']); const hasFloatLeft = float === 'left'; const hasFloatRight = float === 'right'; @@ -49,12 +48,7 @@ export const EuiImageWrapper: FunctionComponent = ({ const euiTheme = useEuiTheme(); - const styles = euiImageWrapperStyles( - euiTheme, - hasFloatLeft, - hasFloatRight, - isSmallScreen - ); + const styles = euiImageWrapperStyles(euiTheme, hasFloatLeft, hasFloatRight); const cssFigureStyles = [ styles.euiImageWrapper, float && styles[float], From 740563f28f012cb762dbab735cf2199f6bc6e733 Mon Sep 17 00:00:00 2001 From: Constance Chen Date: Thu, 14 Jul 2022 12:04:33 -0700 Subject: [PATCH 28/48] Update snapshots --- .../datagrid/body/__snapshots__/data_grid_cell.test.tsx.snap | 2 +- .../modal/__snapshots__/confirm_modal.test.tsx.snap | 2 +- src/components/text/__snapshots__/text.test.tsx.snap | 4 ++-- .../tree_view/__snapshots__/tree_view.test.tsx.snap | 4 ++-- 4 files changed, 6 insertions(+), 6 deletions(-) diff --git a/src/components/datagrid/body/__snapshots__/data_grid_cell.test.tsx.snap b/src/components/datagrid/body/__snapshots__/data_grid_cell.test.tsx.snap index c81c3cfca65..dfa2c0f6161 100644 --- a/src/components/datagrid/body/__snapshots__/data_grid_cell.test.tsx.snap +++ b/src/components/datagrid/body/__snapshots__/data_grid_cell.test.tsx.snap @@ -3,7 +3,7 @@ exports[`EuiDataGridCell componentDidUpdate handles the cell popover by forwarding the cell's DOM node and contents to the parent popover context 1`] = ` Array [
{ +export const euiImageButtonStyles = (euiThemeContext: UseEuiTheme) => { const { euiTheme } = euiThemeContext; return { @@ -24,45 +21,65 @@ export const euiImageButtonStyles = ( cursor: pointer; line-height: 0; + // Shadow on hover - use a pseudo element & opacity for maximum animation performance + &::before { + opacity: 0; + content: ''; + pointer-events: none; // Prevent interacting with this element, it's for visual effect only + position: absolute; // Skip logical properties here - should all be the same + top: 0; + bottom: 0; + left: 0; + right: 0; + + ${euiCanAnimate} { + transition: opacity ${euiTheme.animation.fast} + ${euiTheme.animation.resistance}; + } + } + &:hover, &:focus { - ${hasShadow - ? `${euiShadow(euiThemeContext, 'm')};` - : `${euiShadow(euiThemeContext, 's')};`} - } + &::before { + opacity: 1; + } - ${euiCanAnimate} { - transition: box-shadow ${euiTheme.animation.fast} - ${euiTheme.animation.resistance}; + [class*='euiImageButton__icon'] { + opacity: 1; + } } &:focus { ${euiFocusRing(euiTheme, 'outset')} } - - &:hover [class*='euiImageButton__icon'] { - visibility: visible; - fill-opacity: 1; - } `, fullWidth: css` ${logicalCSS('width', '100%')} `, + shadowHover: css` + &::before { + ${euiShadow(euiThemeContext, 's')} + } + `, + hasShadowHover: css` + &::before { + ${euiShadow(euiThemeContext, 'm')} + } + `, }; }; export const euiImageButtonIconStyles = ({ euiTheme }: UseEuiTheme) => ({ // Base euiImageButton__icon: css` - visibility: hidden; - fill-opacity: 0; + opacity: 0; position: absolute; ${logicalCSS('top', euiTheme.size.base)}; ${logicalCSS('right', euiTheme.size.base)}; cursor: pointer; ${euiCanAnimate} { - transition: fill-opacity ${euiTheme.animation.slow} + transition: opacity ${euiTheme.animation.slow} ${euiTheme.animation.resistance}; } `, diff --git a/src/components/image/image_button.tsx b/src/components/image/image_button.tsx index b9b5989d2ef..ce73a201bf2 100644 --- a/src/components/image/image_button.tsx +++ b/src/components/image/image_button.tsx @@ -39,10 +39,11 @@ export const EuiImageButton: FunctionComponent = ({ }) => { const euiTheme = useEuiTheme(); - const buttonStyles = euiImageButtonStyles(euiTheme, hasShadow); + const buttonStyles = euiImageButtonStyles(euiTheme); const cssButtonStyles = [ buttonStyles.euiImageButton, + hasShadow ? buttonStyles.hasShadowHover : buttonStyles.shadowHover, !isFullScreen && isFullWidth && buttonStyles.fullWidth, ]; @@ -62,7 +63,9 @@ export const EuiImageButton: FunctionComponent = ({ > {children} {allowFullScreen && !isFullScreen && ( - +
+ +
)} ); From b3e4825f191163ca9227b4e66487c4efcf5df5d0 Mon Sep 17 00:00:00 2001 From: Constance Chen Date: Thu, 14 Jul 2022 15:35:44 -0700 Subject: [PATCH 33/48] Fix several props being incorrectly passed to underlying `img` tag as an HTML attribute + refactor unnecessary `:` syntax --- .../image/__snapshots__/image.test.tsx.snap | 5 --- src/components/image/image.tsx | 40 ++++++++----------- 2 files changed, 17 insertions(+), 28 deletions(-) diff --git a/src/components/image/__snapshots__/image.test.tsx.snap b/src/components/image/__snapshots__/image.test.tsx.snap index 6d9fd140d1c..04fccda72e3 100644 --- a/src/components/image/__snapshots__/image.test.tsx.snap +++ b/src/components/image/__snapshots__/image.test.tsx.snap @@ -56,7 +56,6 @@ exports[`EuiImage is rendered with a float 1`] = ` alt
alt @@ -124,7 +121,6 @@ exports[`EuiImage is rendered with src 1`] = ` alt
alt diff --git a/src/components/image/image.tsx b/src/components/image/image.tsx index 6f967b08f26..1b9ee435bc3 100644 --- a/src/components/image/image.tsx +++ b/src/components/image/image.tsx @@ -14,13 +14,7 @@ import { useEuiTheme } from '../../services'; import { EuiImageWrapper } from './image_wrapper'; import { euiImageStyles } from './image.styles'; import { EuiImageFullScreenWrapper } from './image_fullscreen_wrapper'; -import type { - EuiImageProps, - ImageProps, - EuiImageWrapperProps, - EuiImageFullScreenWrapperProps, - EuiImageSize, -} from './image_types'; +import type { EuiImageProps, ImageProps, EuiImageSize } from './image_types'; import { SIZES } from './image_types'; @@ -35,6 +29,9 @@ export const EuiImage: FunctionComponent = ({ wrapperProps, fullScreenIconColor, allowFullScreen, + caption, + float, + margin, ...rest }) => { const [isFullScreen, setIsFullScreen] = useState(false); @@ -72,14 +69,17 @@ export const EuiImage: FunctionComponent = ({ const isFullWidth = size === 'fullWidth'; const commonWrapperProps = { - alt: alt, - hasShadow: hasShadow, - wrapperProps: wrapperProps, - isFullScreen: isFullScreen, - setIsFullScreen: setIsFullScreen, - fullScreenIconColor: fullScreenIconColor, - isFullWidth: isFullWidth, - allowFullScreen: allowFullScreen, + alt, + hasShadow, + wrapperProps, + isFullScreen, + setIsFullScreen, + fullScreenIconColor, + isFullWidth, + allowFullScreen, + caption, + float, + margin, }; const commonImgProps = { @@ -91,18 +91,12 @@ export const EuiImage: FunctionComponent = ({ return ( <> - + {alt} {allowFullScreen && isFullScreen && ( - + {alt} )} From e3533cf32e2b1d5e7149595c14971e6194203a8a Mon Sep 17 00:00:00 2001 From: Constance Chen Date: Thu, 14 Jul 2022 14:20:16 -0700 Subject: [PATCH 34/48] Fix alignment issue between images and captions on mobile - this solution also allows consumers to override text alignment on the figure wrapper quickly via their own CSS or our utils --- src-docs/src/views/image/image_size.js | 1 + src/components/image/image_button.styles.ts | 1 + src/components/image/image_caption.styles.ts | 7 +------ src/components/image/image_caption.tsx | 4 ++-- src/components/image/image_fullscreen_wrapper.styles.ts | 7 ++++++- src/components/image/image_wrapper.styles.ts | 7 ++++++- 6 files changed, 17 insertions(+), 10 deletions(-) diff --git a/src-docs/src/views/image/image_size.js b/src-docs/src/views/image/image_size.js index 1ff44417c44..f4b6a1a5d5b 100644 --- a/src-docs/src/views/image/image_size.js +++ b/src-docs/src/views/image/image_size.js @@ -11,6 +11,7 @@ export default () => ( caption="Custom size (50)" alt="Accessible image alt goes here" src="https://source.unsplash.com/1000x1000/?Nature" + wrapperProps={{ className: 'eui-textLeft' }} /> { euiImageButton: css` position: relative; cursor: pointer; + text-align: match-parent; line-height: 0; // Shadow on hover - use a pseudo element & opacity for maximum animation performance diff --git a/src/components/image/image_caption.styles.ts b/src/components/image/image_caption.styles.ts index dce19611733..87ce8bc1344 100644 --- a/src/components/image/image_caption.styles.ts +++ b/src/components/image/image_caption.styles.ts @@ -7,11 +7,7 @@ */ import { css } from '@emotion/react'; -import { - euiFontSize, - logicalCSS, - logicalTextAlignCSS, -} from '../../global_styling'; +import { euiFontSize, logicalCSS } from '../../global_styling'; import { UseEuiTheme, transparentize } from '../../services'; export const euiImageCaptionStyles = (euiThemeContext: UseEuiTheme) => { @@ -22,7 +18,6 @@ export const euiImageCaptionStyles = (euiThemeContext: UseEuiTheme) => { euiImageCaption: css` ${euiFontSize(euiThemeContext, 's')}; ${logicalCSS('margin-top', euiTheme.size.xs)}; - ${logicalTextAlignCSS('center')}; `, isOnOverlayMask: css` color: ${euiTheme.colors.ghost}; diff --git a/src/components/image/image_caption.tsx b/src/components/image/image_caption.tsx index 44cbe8b8913..d27db0505d2 100644 --- a/src/components/image/image_caption.tsx +++ b/src/components/image/image_caption.tsx @@ -17,13 +17,13 @@ export const EuiImageCaption = forwardRef( ({ caption, isOnOverlayMask = false }, ref: Ref) => { const euiTheme = useEuiTheme(); const styles = euiImageCaptionStyles(euiTheme); - const cssstyles = [ + const cssStyles = [ styles.euiImageCaption, isOnOverlayMask && styles.isOnOverlayMask, ]; return ( -
+
{caption}
); diff --git a/src/components/image/image_fullscreen_wrapper.styles.ts b/src/components/image/image_fullscreen_wrapper.styles.ts index c5ad963cac5..6ba20da65b8 100644 --- a/src/components/image/image_fullscreen_wrapper.styles.ts +++ b/src/components/image/image_fullscreen_wrapper.styles.ts @@ -7,7 +7,11 @@ */ import { css, keyframes } from '@emotion/react'; -import { logicalCSS, euiCanAnimate } from '../../global_styling'; +import { + logicalCSS, + logicalTextAlignCSS, + euiCanAnimate, +} from '../../global_styling'; import { UseEuiTheme } from '../../services'; export const euiImageFullscreenWrapperStyles = ( @@ -25,6 +29,7 @@ export const euiImageFullscreenWrapperStyles = ( position: relative; ${logicalCSS('max-height', '80vh')}; ${logicalCSS('max-width', '80vh')}; + ${logicalTextAlignCSS('center')}; // Aligns both caption and image ${euiCanAnimate} { animation: ${euiImageFullScreen(euiTheme.size.xxxxl)} diff --git a/src/components/image/image_wrapper.styles.ts b/src/components/image/image_wrapper.styles.ts index f751ccecb90..44edcb2360d 100644 --- a/src/components/image/image_wrapper.styles.ts +++ b/src/components/image/image_wrapper.styles.ts @@ -7,7 +7,11 @@ */ import { css } from '@emotion/react'; -import { logicalCSS, logicalSide } from '../../global_styling'; +import { + logicalCSS, + logicalTextAlignCSS, + logicalSide, +} from '../../global_styling'; import { UseEuiTheme } from '../../services'; export const euiImageWrapperStyles = (euiThemeContext: UseEuiTheme) => { @@ -18,6 +22,7 @@ export const euiImageWrapperStyles = (euiThemeContext: UseEuiTheme) => { euiImageWrapper: css` display: inline-block; ${logicalCSS('max-width', '100%')} + ${logicalTextAlignCSS('center')}; // Aligns both caption and image position: relative; line-height: 0; // Fixes cropping when image is resized by forcing its height to be determined by the image not line-height flex-shrink: 0; // Don't ever let this shrink in height if direct descendent of flex From 4445873479aefa1d21fd6f95d9caa5036bfead33 Mon Sep 17 00:00:00 2001 From: Constance Chen Date: Thu, 14 Jul 2022 15:40:34 -0700 Subject: [PATCH 35/48] Fix floated image margins not collapsing w/ preceding paragraph on mobile --- src/components/image/image_wrapper.styles.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/components/image/image_wrapper.styles.ts b/src/components/image/image_wrapper.styles.ts index 44edcb2360d..2c43ee68a4c 100644 --- a/src/components/image/image_wrapper.styles.ts +++ b/src/components/image/image_wrapper.styles.ts @@ -20,7 +20,7 @@ export const euiImageWrapperStyles = (euiThemeContext: UseEuiTheme) => { return { // Base euiImageWrapper: css` - display: inline-block; + display: table; // inline-block causes margins not to correctly collapse ${logicalCSS('max-width', '100%')} ${logicalTextAlignCSS('center')}; // Aligns both caption and image position: relative; From 825955cb22f35d58ec6b6e783d6f890e72a0f956 Mon Sep 17 00:00:00 2001 From: Constance Chen Date: Thu, 14 Jul 2022 15:42:58 -0700 Subject: [PATCH 36/48] Remove unnecessary CSS on figure wrappers - `position: relative` does nothing AFAICT, it's already present on the button which is the only thing that has absolute children - `display: inline-block` on the fullscreen wrapper is not necessary and in fact causes a weird offset - `flex-shrink` on the fullscreen wrapper is also not necessary because we control what's in the fullscreen overlay and there's nothing other than the figure in there --- src/components/image/image_fullscreen_wrapper.styles.ts | 6 +----- src/components/image/image_wrapper.styles.ts | 1 - 2 files changed, 1 insertion(+), 6 deletions(-) diff --git a/src/components/image/image_fullscreen_wrapper.styles.ts b/src/components/image/image_fullscreen_wrapper.styles.ts index 6ba20da65b8..da8d832c202 100644 --- a/src/components/image/image_fullscreen_wrapper.styles.ts +++ b/src/components/image/image_fullscreen_wrapper.styles.ts @@ -22,14 +22,10 @@ export const euiImageFullscreenWrapperStyles = ( return { // Base euiImageFullscreenWrapper: css` - display: inline-block; - position: relative; - line-height: 0; // Fixes cropping when image is resized by forcing its height to be determined by the image not line-height - flex-shrink: 0; // Don't ever let this shrink in height if direct descendent of flex - position: relative; ${logicalCSS('max-height', '80vh')}; ${logicalCSS('max-width', '80vh')}; ${logicalTextAlignCSS('center')}; // Aligns both caption and image + line-height: 0; // Fixes cropping when image is resized by forcing its height to be determined by the image not line-height ${euiCanAnimate} { animation: ${euiImageFullScreen(euiTheme.size.xxxxl)} diff --git a/src/components/image/image_wrapper.styles.ts b/src/components/image/image_wrapper.styles.ts index 2c43ee68a4c..5151ddd1913 100644 --- a/src/components/image/image_wrapper.styles.ts +++ b/src/components/image/image_wrapper.styles.ts @@ -23,7 +23,6 @@ export const euiImageWrapperStyles = (euiThemeContext: UseEuiTheme) => { display: table; // inline-block causes margins not to correctly collapse ${logicalCSS('max-width', '100%')} ${logicalTextAlignCSS('center')}; // Aligns both caption and image - position: relative; line-height: 0; // Fixes cropping when image is resized by forcing its height to be determined by the image not line-height flex-shrink: 0; // Don't ever let this shrink in height if direct descendent of flex `, From f3c0db9bc3ab71491d6a2344660564724c1701fd Mon Sep 17 00:00:00 2001 From: Constance Chen Date: Thu, 14 Jul 2022 17:29:48 -0700 Subject: [PATCH 37/48] Clean up types - Instead of EuiImageProps attempting to inheriting from its children, have EuiImageProps be the source of props truth and have child components Pick<> from EuiImageProps and then filling in their own props - add unit test for exclusive src/url union --- src/components/image/image.test.tsx | 5 + src/components/image/image.tsx | 4 +- .../image/image_fullscreen_wrapper.tsx | 4 +- src/components/image/image_types.ts | 148 ++++++++---------- 4 files changed, 75 insertions(+), 86 deletions(-) diff --git a/src/components/image/image.test.tsx b/src/components/image/image.test.tsx index 43e8816a564..f665260502e 100644 --- a/src/components/image/image.test.tsx +++ b/src/components/image/image.test.tsx @@ -48,6 +48,11 @@ describe('EuiImage', () => { expect(component).toMatchSnapshot(); }); + test('should throw a typescript error when both src and url are passed', () => { + // @ts-expect-error - 'types of property url are incompatible' + render(); + }); + test('is rendered with a float', () => { const component = render( diff --git a/src/components/image/image.tsx b/src/components/image/image.tsx index 1b9ee435bc3..72c8e1c6e76 100644 --- a/src/components/image/image.tsx +++ b/src/components/image/image.tsx @@ -14,7 +14,7 @@ import { useEuiTheme } from '../../services'; import { EuiImageWrapper } from './image_wrapper'; import { euiImageStyles } from './image.styles'; import { EuiImageFullScreenWrapper } from './image_fullscreen_wrapper'; -import type { EuiImageProps, ImageProps, EuiImageSize } from './image_types'; +import type { EuiImageProps, EuiImageSize } from './image_types'; import { SIZES } from './image_types'; @@ -87,7 +87,7 @@ export const EuiImage: FunctionComponent = ({ style: imageStyle, src: src || url, ...rest, - } as ImageProps; + }; return ( <> diff --git a/src/components/image/image_fullscreen_wrapper.tsx b/src/components/image/image_fullscreen_wrapper.tsx index d7ac9cc5249..f776b168191 100644 --- a/src/components/image/image_fullscreen_wrapper.tsx +++ b/src/components/image/image_fullscreen_wrapper.tsx @@ -21,12 +21,12 @@ import { euiImageFullscreenWrapperFullScreenCloseIconStyles, } from './image_fullscreen_wrapper.styles'; -import type { EuiImageFullScreenWrapperProps } from './image_types'; +import type { EuiImageWrapperProps } from './image_types'; import { EuiImageButton } from './image_button'; import { EuiImageCaption } from './image_caption'; -export const EuiImageFullScreenWrapper: FunctionComponent = ({ +export const EuiImageFullScreenWrapper: FunctionComponent = ({ hasShadow, caption, alt, diff --git a/src/components/image/image_types.ts b/src/components/image/image_types.ts index 9e0f62a12fc..dc508c4fe11 100644 --- a/src/components/image/image_types.ts +++ b/src/components/image/image_types.ts @@ -20,43 +20,7 @@ export type EuiImageWrapperMargin = typeof MARGINS[number]; export type EuiImageButtonIconColor = 'light' | 'dark'; -export type EuiImageCommonWrapperProps = CommonProps & { - /** - * Separate from the caption is a title on the alt tag itself. - * This one is required for accessibility. - */ - alt: string; - /** - * When set to `true` (default) will apply a slight shadow to the image - */ - hasShadow?: boolean; - /** - * When set to `true` will make the image clickable to a larger version - */ - allowFullScreen?: boolean; - /** - * Props to add to the wrapping figure element - */ - wrapperProps?: HTMLAttributes; - setIsFullScreen: (isFullScreen: boolean) => void; -}; - -export type EuiImageAllowFullScreenProps = { - /** - * When set to `true` will make the image clickable to a larger version - */ - allowFullScreen?: boolean; - /** - * Float the image to the left or right. Useful in large text blocks. - */ - float?: EuiImageWrapperFloat; - /** - * Margin around the image. - */ - margin?: EuiImageWrapperMargin; -}; - -export type _EuiImageSrcOrUrl = ExclusiveUnion< +type _EuiImageSrcOrUrl = ExclusiveUnion< { /** * Requires either `src` or `url` but defaults to using `src` if both are provided @@ -68,57 +32,77 @@ export type _EuiImageSrcOrUrl = ExclusiveUnion< } >; -export type EuiImageButtonProps = CommonProps & { - /** - * When set to `true` (default) will apply a slight shadow to the image - */ - hasShadow?: boolean; - /** - * Changes the color of the icon that floats above the image when it can be clicked to fullscreen. - * The default value of `light` is fine unless your image has a white background, in which case you should change it to `dark`. - */ - fullScreenIconColor?: EuiImageButtonIconColor; - onClick: () => void; - onKeyDown?: (e: React.KeyboardEvent) => void; - isFullScreen: boolean; - isFullWidth: boolean; - allowFullScreen: boolean | undefined; -}; - -export type EuiImageCaptionProps = { - /** - * Provides the visible caption to the image - */ - caption?: ReactNode; - isOnOverlayMask?: boolean; -}; - -export type ImageProps = CommonProps & - _EuiImageSrcOrUrl & - Omit, 'src'>; - -export type EuiImageProps = Omit & - EuiImageAllowFullScreenProps & - Omit & - Omit & - Omit< - EuiImageButtonProps, - 'onClick' | 'onKeyDown' | 'isFullScreen' | 'isFullWidth' | 'allowFullScreen' - > & { +export type EuiImageProps = CommonProps & + Omit, 'src' | 'alt'> & + _EuiImageSrcOrUrl & { + /** + * Separate from the caption is a title on the alt tag itself. + * This one is required for accessibility. + */ + alt: string; + /** + * Provides a visible caption to the image + */ + caption?: ReactNode; /** * Accepts `s` / `m` / `l` / `xl` / `original` / `fullWidth` / or a CSS size of `number` or `string`. * `fullWidth` will set the figure to stretch to 100% of its container. * `string` and `number` types will max both the width or height, whichever is greater. */ size?: EuiImageSize | number | string; + /** + * Float the image to the left or right. Useful in large text blocks. + */ + float?: EuiImageWrapperFloat; + /** + * Margin around the image. + */ + margin?: EuiImageWrapperMargin; + /** + * When set to `true` (default) will apply a slight shadow to the image + */ + hasShadow?: boolean; + /** + * When set to `true` will make the image clickable to a larger version + */ + allowFullScreen?: boolean; + /** + * Changes the color of the icon that floats above the image when it can be clicked to fullscreen. + * The default value of `light` is fine unless your image has a white background, in which case you should change it to `dark`. + */ + fullScreenIconColor?: EuiImageButtonIconColor; + /** + * Props to add to the wrapping figure element + */ + wrapperProps?: HTMLAttributes; }; -export type EuiImageWrapperProps = EuiImageCommonWrapperProps & - EuiImageAllowFullScreenProps & - Omit & - EuiImageCaptionProps; +export type EuiImageWrapperProps = Pick< + EuiImageProps, + | 'alt' + | 'caption' + | 'float' + | 'margin' + | 'hasShadow' + | 'wrapperProps' + | 'fullScreenIconColor' + | 'allowFullScreen' +> & { + isFullWidth: boolean; + isFullScreen: boolean; + setIsFullScreen: (isFullScreen: boolean) => void; +}; + +export type EuiImageButtonProps = Pick< + EuiImageProps, + 'hasShadow' | 'allowFullScreen' | 'fullScreenIconColor' +> & { + onClick: () => void; + onKeyDown?: (e: React.KeyboardEvent) => void; + isFullWidth: boolean; + isFullScreen: boolean; +}; -export type EuiImageFullScreenWrapperProps = EuiImageCommonWrapperProps & - EuiImageAllowFullScreenProps & - Omit & - EuiImageCaptionProps; +export type EuiImageCaptionProps = Pick & { + isOnOverlayMask?: boolean; +}; From 2c11b45c077aebcb7651b047072503dc92703f73 Mon Sep 17 00:00:00 2001 From: Constance Chen Date: Fri, 15 Jul 2022 00:20:24 -0700 Subject: [PATCH 38/48] Improve unit tests - add more branch coverage - add describe block for all props, including missing hasShadow and fullScreenIconColor props - DRY out required props for EuiImage --- .../image/__snapshots__/image.test.tsx.snap | 149 +++++++++++--- src/components/image/image.test.tsx | 189 ++++++++++-------- 2 files changed, 223 insertions(+), 115 deletions(-) diff --git a/src/components/image/__snapshots__/image.test.tsx.snap b/src/components/image/__snapshots__/image.test.tsx.snap index 04fccda72e3..242555f7e90 100644 --- a/src/components/image/__snapshots__/image.test.tsx.snap +++ b/src/components/image/__snapshots__/image.test.tsx.snap @@ -5,9 +5,9 @@ exports[`EuiImage is rendered 1`] = ` class="euiImageWrapper emotion-euiImageWrapper" > alt @@ -17,20 +17,20 @@ exports[`EuiImage is rendered 1`] = `
`; -exports[`EuiImage is rendered and allows fullscreen 1`] = ` +exports[`EuiImage props allowFullScreen 1`] = `
`; -exports[`EuiImage is rendered with a float 1`] = ` +exports[`EuiImage props caption 1`] = ` +
+ +
+ + caption + +
+
+`; + +exports[`EuiImage props float 1`] = `
alt
`; -exports[`EuiImage is rendered with a margin 1`] = ` +exports[`EuiImage props fullScreenIconColor 1`] = ` +
+ +
+
+`; + +exports[`EuiImage props hasShadow 1`] = ` +
+ +
+
+`; + +exports[`EuiImage props margin 1`] = `
alt
`; -exports[`EuiImage is rendered with a node as the caption 1`] = ` +exports[`EuiImage props size 1`] = `
alt
- - caption - -
+ />
`; -exports[`EuiImage is rendered with custom size 1`] = ` +exports[`EuiImage props src vs url src 1`] = `
alt
`; -exports[`EuiImage is rendered with src 1`] = ` +exports[`EuiImage props src vs url url 1`] = `
alt
`; -exports[`EuiImage is rendered with wrapperProps 1`] = ` +exports[`EuiImage props wrapperProps 1`] = `
{ - shouldRenderCustomStyles(); + const requiredProps = { + ...commonProps, + alt: '', + src: '/cat.jpg', + }; - test('is rendered', () => { - const component = render( - - ); - - expect(component).toMatchSnapshot(); - }); - - test('is rendered and allows fullscreen', () => { - const component = render( - - ); - - expect(component).toMatchSnapshot(); - }); - - test('is rendered with src', () => { - const component = render( - - ); + shouldRenderCustomStyles(); + test('is rendered', () => { + const component = render(); expect(component).toMatchSnapshot(); }); - test('should throw a typescript error when both src and url are passed', () => { - // @ts-expect-error - 'types of property url are incompatible' - render(); - }); + describe('props', () => { + describe('src vs url', () => { + test('src', () => { + const component = render(); + expect(component).toMatchSnapshot(); + }); - test('is rendered with a float', () => { - const component = render( - - ); + test('url', () => { + const component = render(); + expect(component).toMatchSnapshot(); + }); - expect(component).toMatchSnapshot(); - }); + it('picks src over url when both are present (and throws a typescript error)', () => { + const component = render( + // @ts-expect-error - 'types of property url are incompatible' + + ); + expect(component.find('img').attr('src')).toEqual('/dog.jpg'); + }); + }); - test('is rendered with a margin', () => { - const component = render(); + test('float', () => { + const component = render(); + expect(component).toMatchSnapshot(); + }); - expect(component).toMatchSnapshot(); - }); + test('margin', () => { + const component = render(); + expect(component).toMatchSnapshot(); + }); - test('is rendered with custom size', () => { - const component = render(); + test('size', () => { + const component = render(); + expect(component).toMatchSnapshot(); + }); - expect(component).toMatchSnapshot(); - }); + test('caption', () => { + const component = render( + caption} /> + ); + expect(component).toMatchSnapshot(); + }); - test('is rendered with a node as the caption', () => { - const component = render( - caption} url="/cat.jpg" /> - ); + test('allowFullScreen', () => { + const component = render(); + expect(component).toMatchSnapshot(); + }); - expect(component).toMatchSnapshot(); - }); + test('fullScreenIconColor', () => { + const component = render( + + ); + expect(component).toMatchSnapshot(); + }); - test('is rendered with wrapperProps', () => { - const component = render( - caption} - url="/cat.jpg" - wrapperProps={{ - ...requiredProps, - style: { border: '2px solid red' }, - }} - /> - ); + test('hasShadow', () => { + const component = render( + + ); + expect(component).toMatchSnapshot(); + }); - expect(component).toMatchSnapshot(); + test('wrapperProps', () => { + const component = render( + caption} + url="/cat.jpg" + wrapperProps={{ + ...requiredProps, + style: { border: '2px solid red' }, + }} + /> + ); + expect(component).toMatchSnapshot(); + }); }); - describe('Fullscreen behaviour', () => { + describe('fullscreen behaviour', () => { let component: ReactWrapper; beforeAll(() => { - const testProps = { - ...requiredProps, - 'data-test-subj': 'euiImage', - }; - component = mount( ); }); @@ -127,9 +139,7 @@ describe('EuiImage', () => { ); expect(overlayMask.length).toBe(1); - const fullScreenImage = overlayMask[0].querySelectorAll( - '[data-test-subj=euiImage]' - ); + const fullScreenImage = overlayMask[0].querySelectorAll('figure img'); expect(fullScreenImage.length).toBe(1); }); @@ -150,25 +160,34 @@ describe('EuiImage', () => { }); test('close using ESCAPE key', () => { - const deactivateFullScreenBtn = document.querySelectorAll( + const deactivateFullScreenBtn = document.querySelector( '[data-test-subj=deactivateFullScreenButton]' ); - expect(deactivateFullScreenBtn.length).toBe(1); + expect(deactivateFullScreenBtn).toBeTruthy(); + // Ignores non-escape keys + act(() => { + const escapeKeydownEvent = new KeyboardEvent('keydown', { + key: keys.TAB, + bubbles: true, + }); + deactivateFullScreenBtn!.dispatchEvent(escapeKeydownEvent); + }); + expect(deactivateFullScreenBtn).toBeTruthy(); + + // Removes full screen overlay on escape key act(() => { const escapeKeydownEvent = new KeyboardEvent('keydown', { key: keys.ESCAPE, bubbles: true, }); - (deactivateFullScreenBtn[0] as HTMLElement).dispatchEvent( - escapeKeydownEvent - ); + deactivateFullScreenBtn!.dispatchEvent(escapeKeydownEvent); }); - const overlayMask = document.querySelectorAll( + const overlayMask = document.querySelector( '[data-test-subj=fullScreenOverlayMask]' ); - expect(overlayMask.length).toBe(0); + expect(overlayMask).toBeFalsy(); }); test('close using overlay mask', () => { From 023d82c44c768cb802b6fdd1ee264349d8b3d123 Mon Sep 17 00:00:00 2001 From: Constance Chen Date: Mon, 18 Jul 2022 11:25:41 -0700 Subject: [PATCH 39/48] Improve accessibilty documentation and screen reader experience for EuiImage - Add callout around `alt` text + detailed props documentation + link to more detailed article - Update examples to use static images instead of random ones, which allows us to give an example of meaningful alt text & captions + add inline comments when empty alt text is passed - Fix fullscreen button to not read out entire `alt` (not correct use of alt) and also to use aria-describedby over aria-labelled (has a gentler SR experience when `alt` is present) --- src-docs/src/views/image/float.js | 8 +-- src-docs/src/views/image/image.js | 10 +++- src-docs/src/views/image/image_example.js | 55 ++++++++++++++----- src-docs/src/views/image/image_size.js | 33 ++++++----- src-docs/src/views/image/image_zoom.js | 12 ++-- .../image/__snapshots__/image.test.tsx.snap | 24 +++++++- src/components/image/image.tsx | 1 - .../image/image_fullscreen_wrapper.tsx | 20 +++---- src/components/image/image_types.ts | 9 ++- src/components/image/image_wrapper.tsx | 45 +++++++-------- 10 files changed, 138 insertions(+), 79 deletions(-) diff --git a/src-docs/src/views/image/float.js b/src-docs/src/views/image/float.js index cc29381f5a5..deac633e19d 100644 --- a/src-docs/src/views/image/float.js +++ b/src-docs/src/views/image/float.js @@ -10,9 +10,9 @@ export default () => ( float="right" margin="l" hasShadow - caption="Random nature image" + caption="A randomized image" allowFullScreen - alt="Random nature image" + alt="" // Because the image is randomized, there is no meaningful alt text we can generate here. src="https://picsum.photos/800/500" />

{fake('{{lorem.paragraphs}}')}

@@ -24,8 +24,8 @@ export default () => ( margin="l" hasShadow allowFullScreen - caption="Another random image" - alt="Random nature image" + caption="Another randomized image" + alt="" // Because the image is randomized, there is no meaningful alt text we can generate here. src="https://picsum.photos/300/300" />

{fake('{{lorem.paragraphs}}')}

diff --git a/src-docs/src/views/image/image.js b/src-docs/src/views/image/image.js index a4a1820fb15..6ef1d5ed428 100644 --- a/src-docs/src/views/image/image.js +++ b/src-docs/src/views/image/image.js @@ -6,8 +6,12 @@ export default () => ( + Mastigias papua, also known as spotted jelly +

+ } + alt="Many small white-spotted pink jellyfish floating in a dark aquarium" + src="https://images.unsplash.com/photo-1650253618249-fb0d32d3865c?w=900&h=900&fit=crop&q=60" /> ); diff --git a/src-docs/src/views/image/image_example.js b/src-docs/src/views/image/image_example.js index c2aeba36dae..721f7cdced2 100644 --- a/src-docs/src/views/image/image_example.js +++ b/src-docs/src/views/image/image_example.js @@ -2,7 +2,12 @@ import React, { Fragment } from 'react'; import { GuideSectionTypes } from '../../components'; -import { EuiCode, EuiCallOut, EuiImage } from '../../../../src/components'; +import { + EuiCode, + EuiCallOut, + EuiLink, + EuiImage, +} from '../../../../src/components'; EuiImage.__docgenInfo.props.src.required = true; import imageConfig from './playground'; @@ -10,8 +15,8 @@ import imageConfig from './playground'; import Image from './image'; const imageSource = require('!!raw-loader!./image'); const imageSnippet = ` `; @@ -19,8 +24,8 @@ import ImageSizes from './image_size'; const imageSizesSource = require('!!raw-loader!./image_size'); const imageSizesSnippet = ` `; @@ -28,16 +33,16 @@ import ImageZoom from './image_zoom'; const imageZoomSource = require('!!raw-loader!./image_zoom'); const imageZoomSnippet = ` `; import ImageFloat from './float'; const imageFloatSource = require('!!raw-loader!./float'); const imageFloatSnippet = ` @@ -54,10 +59,34 @@ export const ImageExample = { }, ], text: ( -

- Use EuiImage when you need to place a static image - into a page with an optional caption. -

+ <> +

+ Use EuiImage when you need to place a static image + into a page with an optional caption. +

+ +

+ This page has several examples of alt text written to aid screen + reader users, as well as several examples of when not to + include alt text. When no meaningful description exists, or if the + image is adequately described by the surrounding text, it is + better to pass an empty {'""'} string instead. +

+

+ See{' '} + + WebAIM + {' '} + for a more detailed guide to writing effective alt text. +

+
+ ), props: { EuiImage }, demo: , diff --git a/src-docs/src/views/image/image_size.js b/src-docs/src/views/image/image_size.js index f4b6a1a5d5b..262f16378da 100644 --- a/src-docs/src/views/image/image_size.js +++ b/src-docs/src/views/image/image_size.js @@ -2,6 +2,11 @@ import React from 'react'; import { EuiImage, EuiSpacer } from '../../../../src/components'; +const src = + 'https://images.unsplash.com/photo-1477747219299-60f95c811fef?w=1000&h=1000&fit=crop&q=60'; +const alt = + 'A cozy breakfast scene. In the background is a plate of waffles and blueberries. In the middle ground is a glass of orange juice and a small cup of cream. In the foreground is a plate of Eggs Benedict with a side of salad and cherry tomatoes.'; + export default () => (
( allowFullScreen size={50} caption="Custom size (50)" - alt="Accessible image alt goes here" - src="https://source.unsplash.com/1000x1000/?Nature" + alt={alt} + src={src} wrapperProps={{ className: 'eui-textLeft' }} /> @@ -19,8 +24,8 @@ export default () => ( hasShadow allowFullScreen caption="Small" - alt="Accessible image alt goes here" - src="https://source.unsplash.com/1000x1000/?Nature" + alt={alt} + src={src} /> ( hasShadow allowFullScreen caption="Medium" - alt="Accessible image alt goes here" - src="https://source.unsplash.com/1000x1000/?Nature" + alt={alt} + src={src} /> ( hasShadow allowFullScreen caption="Large" - alt="Accessible image alt goes here" - src="https://source.unsplash.com/1000x1000/?Nature" + alt={alt} + src={src} /> ( hasShadow allowFullScreen caption="Extra large" - alt="Accessible image alt goes here" - src="https://source.unsplash.com/1000x1000/?Nature" + alt={alt} + src={src} /> ( allowFullScreen size="fullWidth" caption="Full width" - alt="Accessible image alt goes here" - src="https://source.unsplash.com/1000x1000/?Nature" + alt={alt} + src={src} />
); diff --git a/src-docs/src/views/image/image_zoom.js b/src-docs/src/views/image/image_zoom.js index 5fea5d1f745..10ef51c25fc 100644 --- a/src-docs/src/views/image/image_zoom.js +++ b/src-docs/src/views/image/image_zoom.js @@ -13,9 +13,9 @@ export default () => ( size="m" hasShadow allowFullScreen - caption="Click me" - alt="Accessible image alt goes here" - src="https://source.unsplash.com/2000x1000/?Nature" + caption="Albert Einstein, theoretical physicist" + alt="" // Because this image is sufficiently described by its caption, there is no need to repeat it via alt text + src="https://upload.wikimedia.org/wikipedia/commons/d/d3/Albert_Einstein_Head.jpg" /> @@ -23,10 +23,10 @@ export default () => ( size="m" hasShadow allowFullScreen - caption="Click me" - alt="Accessible image alt goes here" + caption="Marie Curie, physicist and chemist" + alt="" // Because this image is sufficiently described by its caption, there is no need to repeat it via alt text fullScreenIconColor="dark" - src="https://source.unsplash.com/1000x2000/?Nature" + src="https://upload.wikimedia.org/wikipedia/commons/a/a3/Marie_Sklodowska%2C_%C3%A9tudiante%2C_en_1895.jpg" /> diff --git a/src/components/image/__snapshots__/image.test.tsx.snap b/src/components/image/__snapshots__/image.test.tsx.snap index 242555f7e90..12a116636bf 100644 --- a/src/components/image/__snapshots__/image.test.tsx.snap +++ b/src/components/image/__snapshots__/image.test.tsx.snap @@ -22,7 +22,7 @@ exports[`EuiImage props allowFullScreen 1`] = ` class="euiImageWrapper emotion-euiImageWrapper-allowFullScreen" > +
@@ -92,7 +98,7 @@ exports[`EuiImage props fullScreenIconColor 1`] = ` class="euiImageWrapper emotion-euiImageWrapper-allowFullScreen" > +
@@ -124,7 +136,7 @@ exports[`EuiImage props hasShadow 1`] = ` class="euiImageWrapper emotion-euiImageWrapper-allowFullScreen-fullWidth" > +
diff --git a/src/components/image/image.tsx b/src/components/image/image.tsx index 72c8e1c6e76..10469f85f1f 100644 --- a/src/components/image/image.tsx +++ b/src/components/image/image.tsx @@ -69,7 +69,6 @@ export const EuiImage: FunctionComponent = ({ const isFullWidth = size === 'fullWidth'; const commonWrapperProps = { - alt, hasShadow, wrapperProps, isFullScreen, diff --git a/src/components/image/image_fullscreen_wrapper.tsx b/src/components/image/image_fullscreen_wrapper.tsx index f776b168191..00b2a720c20 100644 --- a/src/components/image/image_fullscreen_wrapper.tsx +++ b/src/components/image/image_fullscreen_wrapper.tsx @@ -12,8 +12,8 @@ import classNames from 'classnames'; import { EuiIcon } from '../icon'; import { EuiFocusTrap } from '../focus_trap'; import { EuiOverlayMask } from '../overlay_mask'; -import { useEuiI18n } from '../i18n'; -import { useEuiTheme, keys } from '../../services'; +import { EuiI18n } from '../i18n'; +import { useEuiTheme, useGeneratedHtmlId, keys } from '../../services'; import { useInnerText } from '../inner_text'; import { @@ -29,7 +29,6 @@ import { EuiImageCaption } from './image_caption'; export const EuiImageFullScreenWrapper: FunctionComponent = ({ hasShadow, caption, - alt, children, isFullScreen, setIsFullScreen, @@ -68,13 +67,8 @@ export const EuiImageFullScreenWrapper: FunctionComponent setIsFullScreen(false); }; - const closeImageLabel = useEuiI18n( - 'euiImageFullscreenWrapper.closeImage', - 'Close fullscreen {alt} image', - { alt } - ); - const [optionalCaptionRef, optionalCaptionText] = useInnerText(); + const describedById = useGeneratedHtmlId(); return ( hasShadow={hasShadow} onClick={closeFullScreen} onKeyDown={onKeyDown} - aria-label={closeImageLabel} + aria-describedby={describedById} data-test-subj="deactivateFullScreenButton" isFullScreen={isFullScreen} isFullWidth={isFullWidth} @@ -102,6 +96,12 @@ export const EuiImageFullScreenWrapper: FunctionComponent > {children} + , 'src' | 'alt'> & _EuiImageSrcOrUrl & { /** - * Separate from the caption is a title on the alt tag itself. - * This one is required for accessibility. + * Alt text should describe the image to aid screen reader users. See + * https://webaim.org/techniques/alttext/ for a guide on writing + * effective alt text. + * + * If no meaningful description exists, or if the image is adequately + * described by the surrounding text, pass an empty string. */ alt: string; /** @@ -79,7 +83,6 @@ export type EuiImageProps = CommonProps & export type EuiImageWrapperProps = Pick< EuiImageProps, - | 'alt' | 'caption' | 'float' | 'margin' diff --git a/src/components/image/image_wrapper.tsx b/src/components/image/image_wrapper.tsx index 7af6d53c8a0..b29842eda5a 100644 --- a/src/components/image/image_wrapper.tsx +++ b/src/components/image/image_wrapper.tsx @@ -9,9 +9,8 @@ import React, { FunctionComponent } from 'react'; import classNames from 'classnames'; -import { useEuiI18n } from '../i18n'; - -import { useEuiTheme } from '../../services'; +import { EuiI18n } from '../i18n'; +import { useEuiTheme, useGeneratedHtmlId } from '../../services'; import { useInnerText } from '../inner_text'; import type { EuiImageWrapperProps } from './image_types'; @@ -24,7 +23,6 @@ export const EuiImageWrapper: FunctionComponent = ({ caption, hasShadow, allowFullScreen, - alt, float, margin, children, @@ -55,12 +53,7 @@ export const EuiImageWrapper: FunctionComponent = ({ ]; const [optionalCaptionRef, optionalCaptionText] = useInnerText(); - - const openImageLabel = useEuiI18n( - 'euiImageWrapper.openImage', - 'Open fullscreen {alt} image', - { alt } - ); + const describedById = useGeneratedHtmlId(); return (
= ({ css={cssFigureStyles} > {allowFullScreen ? ( - - {children} - + <> + + {children} + + + ) : ( children )} From f580c8096196c6a0af3d95b83ecfc5d3d75a24f2 Mon Sep 17 00:00:00 2001 From: Constance Chen Date: Mon, 18 Jul 2022 12:02:33 -0700 Subject: [PATCH 40/48] [Misc] Convert image .js files to .tsx --- src-docs/src/views/image/{float.js => float.tsx} | 1 + src-docs/src/views/image/{image.js => image.tsx} | 0 src-docs/src/views/image/{image_size.js => image_size.tsx} | 0 src-docs/src/views/image/{image_zoom.js => image_zoom.tsx} | 0 4 files changed, 1 insertion(+) rename src-docs/src/views/image/{float.js => float.tsx} (95%) rename src-docs/src/views/image/{image.js => image.tsx} (100%) rename src-docs/src/views/image/{image_size.js => image_size.tsx} (100%) rename src-docs/src/views/image/{image_zoom.js => image_zoom.tsx} (100%) diff --git a/src-docs/src/views/image/float.js b/src-docs/src/views/image/float.tsx similarity index 95% rename from src-docs/src/views/image/float.js rename to src-docs/src/views/image/float.tsx index deac633e19d..2cbee58269c 100644 --- a/src-docs/src/views/image/float.js +++ b/src-docs/src/views/image/float.tsx @@ -1,6 +1,7 @@ import React from 'react'; import { EuiImage, EuiText } from '../../../../src/components'; +// @ts-ignore faker has no Typescript defs import { fake } from 'faker'; export default () => ( diff --git a/src-docs/src/views/image/image.js b/src-docs/src/views/image/image.tsx similarity index 100% rename from src-docs/src/views/image/image.js rename to src-docs/src/views/image/image.tsx diff --git a/src-docs/src/views/image/image_size.js b/src-docs/src/views/image/image_size.tsx similarity index 100% rename from src-docs/src/views/image/image_size.js rename to src-docs/src/views/image/image_size.tsx diff --git a/src-docs/src/views/image/image_zoom.js b/src-docs/src/views/image/image_zoom.tsx similarity index 100% rename from src-docs/src/views/image/image_zoom.js rename to src-docs/src/views/image/image_zoom.tsx From 6f0a7d97c76a6c19ea02ae363a829b2840554b0e Mon Sep 17 00:00:00 2001 From: Constance Chen Date: Mon, 18 Jul 2022 13:49:42 -0700 Subject: [PATCH 41/48] Fix axe lint complaint about fullscreen buttons with no text - we can't use `aria-describedby` unfortunately because that doesn't qualify as a button name - so instead I'm using SR-only text to describe the fullscreen UX (+ with bonus short circuiting in fullscreen mode for images with long alt text) - hasAlt and an em dash gets us somewhat close to previous describedby behavior (adding a pause between img alt and the button behavior description) - I'm also removing `allowFullScreen` from being passed as a prop to image_button - this button can safely assume it's only used/called when fullscreen mode is enabled --- .../image/__snapshots__/image.test.tsx.snap | 42 +++++------ src/components/image/image.test.tsx | 1 + src/components/image/image.tsx | 1 + src/components/image/image_button.styles.ts | 8 ++- src/components/image/image_button.tsx | 70 ++++++++++++++----- .../image/image_fullscreen_wrapper.styles.ts | 13 ---- .../image/image_fullscreen_wrapper.tsx | 45 ++++-------- src/components/image/image_types.ts | 4 +- src/components/image/image_wrapper.tsx | 14 +--- 9 files changed, 101 insertions(+), 97 deletions(-) diff --git a/src/components/image/__snapshots__/image.test.tsx.snap b/src/components/image/__snapshots__/image.test.tsx.snap index 12a116636bf..0f72150156c 100644 --- a/src/components/image/__snapshots__/image.test.tsx.snap +++ b/src/components/image/__snapshots__/image.test.tsx.snap @@ -22,7 +22,6 @@ exports[`EuiImage props allowFullScreen 1`] = ` class="euiImageWrapper emotion-euiImageWrapper-allowFullScreen" > -
@@ -98,7 +96,6 @@ exports[`EuiImage props fullScreenIconColor 1`] = ` class="euiImageWrapper emotion-euiImageWrapper-allowFullScreen" > -
@@ -136,7 +132,6 @@ exports[`EuiImage props hasShadow 1`] = ` class="euiImageWrapper emotion-euiImageWrapper-allowFullScreen-fullWidth" > -
diff --git a/src/components/image/image.test.tsx b/src/components/image/image.test.tsx index 93fe88a22c3..5853d05bb4d 100644 --- a/src/components/image/image.test.tsx +++ b/src/components/image/image.test.tsx @@ -124,6 +124,7 @@ describe('EuiImage', () => { ); diff --git a/src/components/image/image.tsx b/src/components/image/image.tsx index 10469f85f1f..3fe4d48a904 100644 --- a/src/components/image/image.tsx +++ b/src/components/image/image.tsx @@ -76,6 +76,7 @@ export const EuiImage: FunctionComponent = ({ fullScreenIconColor, isFullWidth, allowFullScreen, + alt, caption, float, margin, diff --git a/src/components/image/image_button.styles.ts b/src/components/image/image_button.styles.ts index 946d805690a..219409f9ec2 100644 --- a/src/components/image/image_button.styles.ts +++ b/src/components/image/image_button.styles.ts @@ -73,10 +73,12 @@ export const euiImageButtonStyles = (euiThemeContext: UseEuiTheme) => { export const euiImageButtonIconStyles = ({ euiTheme }: UseEuiTheme) => ({ // Base euiImageButton__icon: css` - opacity: 0; position: absolute; ${logicalCSS('top', euiTheme.size.base)}; ${logicalCSS('right', euiTheme.size.base)}; + `, + openFullScreen: css` + opacity: 0; cursor: pointer; ${euiCanAnimate} { @@ -84,4 +86,8 @@ export const euiImageButtonIconStyles = ({ euiTheme }: UseEuiTheme) => ({ ${euiTheme.animation.resistance}; } `, + closeFullScreen: css` + // Fullscreen close event handled by EuiOverlayMask + pointer-events: none; + `, }); diff --git a/src/components/image/image_button.tsx b/src/components/image/image_button.tsx index ce73a201bf2..796cc4caf54 100644 --- a/src/components/image/image_button.tsx +++ b/src/components/image/image_button.tsx @@ -7,13 +7,16 @@ */ import React, { FunctionComponent } from 'react'; + import { useEuiTheme } from '../../services'; +import { useEuiI18n } from '../i18n'; +import { EuiIcon } from '../icon'; +import { EuiScreenReaderOnly } from '../accessibility'; import { euiImageButtonStyles, euiImageButtonIconStyles, } from './image_button.styles'; -import { EuiIcon } from '../icon'; import type { EuiImageButtonProps, EuiImageButtonIconColor, @@ -27,13 +30,13 @@ const fullScreenIconColorMap: { }; export const EuiImageButton: FunctionComponent = ({ + hasAlt, hasShadow, children, onClick, onKeyDown, isFullScreen, isFullWidth, - allowFullScreen, fullScreenIconColor = 'light', ...rest }) => { @@ -48,25 +51,58 @@ export const EuiImageButton: FunctionComponent = ({ ]; const iconStyles = euiImageButtonIconStyles(euiTheme); - const cssIconStyles = [iconStyles.euiImageButton__icon]; + const cssIconStyles = [ + iconStyles.euiImageButton__icon, + !isFullScreen && iconStyles.openFullScreen, + isFullScreen && iconStyles.closeFullScreen, + ]; + + const openFullScreenInstructions = useEuiI18n( + 'euiImageButton.openFullScreen', + 'Click to open this image in fullscreen mode' + ); + const closeFullScreenInstructions = useEuiI18n( + 'euiImageButton.closeFullScreen', + 'Press Escape or click to close image fullscreen mode' + ); const iconColor = fullScreenIconColorMap[fullScreenIconColor as EuiImageButtonIconColor]; return ( - + <> + + ); }; diff --git a/src/components/image/image_fullscreen_wrapper.styles.ts b/src/components/image/image_fullscreen_wrapper.styles.ts index da8d832c202..69fc9e6033e 100644 --- a/src/components/image/image_fullscreen_wrapper.styles.ts +++ b/src/components/image/image_fullscreen_wrapper.styles.ts @@ -43,19 +43,6 @@ export const euiImageFullscreenWrapperStyles = ( }; }; -export const euiImageFullscreenWrapperFullScreenCloseIconStyles = ({ - euiTheme, -}: UseEuiTheme) => ({ - // Base - euiImageFullscreenWrapper__fullScreenCloseIcon: css` - position: absolute; - ${logicalCSS('top', euiTheme.size.base)}; - ${logicalCSS('right', euiTheme.size.base)}; - pointer-events: none; - fill: ${euiTheme.colors.ghost} !important; - `, -}); - const euiImageFullScreen = (size: string) => keyframes` 0% { opacity: 0; diff --git a/src/components/image/image_fullscreen_wrapper.tsx b/src/components/image/image_fullscreen_wrapper.tsx index 00b2a720c20..8a8c0755d35 100644 --- a/src/components/image/image_fullscreen_wrapper.tsx +++ b/src/components/image/image_fullscreen_wrapper.tsx @@ -9,31 +9,28 @@ import React, { FunctionComponent } from 'react'; import classNames from 'classnames'; -import { EuiIcon } from '../icon'; import { EuiFocusTrap } from '../focus_trap'; import { EuiOverlayMask } from '../overlay_mask'; -import { EuiI18n } from '../i18n'; -import { useEuiTheme, useGeneratedHtmlId, keys } from '../../services'; +import { EuiIcon } from '../icon'; +import { useEuiTheme, keys } from '../../services'; import { useInnerText } from '../inner_text'; -import { - euiImageFullscreenWrapperStyles, - euiImageFullscreenWrapperFullScreenCloseIconStyles, -} from './image_fullscreen_wrapper.styles'; - +import { euiImageFullscreenWrapperStyles } from './image_fullscreen_wrapper.styles'; import type { EuiImageWrapperProps } from './image_types'; import { EuiImageButton } from './image_button'; +import { euiImageButtonIconStyles } from './image_button.styles'; + import { EuiImageCaption } from './image_caption'; export const EuiImageFullScreenWrapper: FunctionComponent = ({ + alt, hasShadow, caption, children, isFullScreen, setIsFullScreen, wrapperProps, - allowFullScreen, isFullWidth, fullScreenIconColor, }) => { @@ -43,13 +40,6 @@ export const EuiImageFullScreenWrapper: FunctionComponent const cssStyles = [styles.euiImageFullscreenWrapper]; - const fullScreenCloseIconStyles = euiImageFullscreenWrapperFullScreenCloseIconStyles( - euiTheme - ); - const cssFullScreenCloseIconStyles = [ - fullScreenCloseIconStyles.euiImageFullscreenWrapper__fullScreenCloseIcon, - ]; - const classes = classNames( 'euiImageFullScreenWrapper', wrapperProps && wrapperProps.className @@ -68,7 +58,12 @@ export const EuiImageFullScreenWrapper: FunctionComponent }; const [optionalCaptionRef, optionalCaptionText] = useInnerText(); - const describedById = useGeneratedHtmlId(); + + const iconStyles = euiImageButtonIconStyles(euiTheme); + const cssIconStyles = [ + iconStyles.euiImageButton__icon, + iconStyles.closeFullScreen, + ]; return ( css={cssStyles} > {children} -
- + {/* Must be outside the `figure` element in order to escape the translateY transition. see https://www.w3.org/TR/css-transforms-1/#transform-rendering */} +
diff --git a/src/components/image/image_types.ts b/src/components/image/image_types.ts index 78bc6ef288f..9f904af5c6b 100644 --- a/src/components/image/image_types.ts +++ b/src/components/image/image_types.ts @@ -83,6 +83,7 @@ export type EuiImageProps = CommonProps & export type EuiImageWrapperProps = Pick< EuiImageProps, + | 'alt' | 'caption' | 'float' | 'margin' @@ -98,8 +99,9 @@ export type EuiImageWrapperProps = Pick< export type EuiImageButtonProps = Pick< EuiImageProps, - 'hasShadow' | 'allowFullScreen' | 'fullScreenIconColor' + 'hasShadow' | 'fullScreenIconColor' > & { + hasAlt: boolean; onClick: () => void; onKeyDown?: (e: React.KeyboardEvent) => void; isFullWidth: boolean; diff --git a/src/components/image/image_wrapper.tsx b/src/components/image/image_wrapper.tsx index b29842eda5a..dbbedb1109e 100644 --- a/src/components/image/image_wrapper.tsx +++ b/src/components/image/image_wrapper.tsx @@ -9,8 +9,7 @@ import React, { FunctionComponent } from 'react'; import classNames from 'classnames'; -import { EuiI18n } from '../i18n'; -import { useEuiTheme, useGeneratedHtmlId } from '../../services'; +import { useEuiTheme } from '../../services'; import { useInnerText } from '../inner_text'; import type { EuiImageWrapperProps } from './image_types'; @@ -20,6 +19,7 @@ import { EuiImageButton } from './image_button'; import { EuiImageCaption } from './image_caption'; export const EuiImageWrapper: FunctionComponent = ({ + alt, caption, hasShadow, allowFullScreen, @@ -53,7 +53,6 @@ export const EuiImageWrapper: FunctionComponent = ({ ]; const [optionalCaptionRef, optionalCaptionText] = useInnerText(); - const describedById = useGeneratedHtmlId(); return (
= ({ {allowFullScreen ? ( <> {children} - ) : ( children From e48d39ec6552b26b013dac2cdcdeac6ecf484ab6 Mon Sep 17 00:00:00 2001 From: Constance Chen Date: Mon, 18 Jul 2022 15:57:36 -0700 Subject: [PATCH 42/48] Copy tweaks --- src-docs/src/views/image/image_example.js | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/src-docs/src/views/image/image_example.js b/src-docs/src/views/image/image_example.js index 721f7cdced2..20651afee76 100644 --- a/src-docs/src/views/image/image_example.js +++ b/src-docs/src/views/image/image_example.js @@ -71,12 +71,14 @@ export const ImageExample = {

This page has several examples of alt text written to aid screen reader users, as well as several examples of when not to - include alt text. When no meaningful description exists, or if the - image is adequately described by the surrounding text, it is - better to pass an empty {'""'} string instead. + include alt text. When an image is decorative, or if the image is + adequately described by surrounding text, it is better to pass an + empty {'""'} string instead.

- See{' '} + When an image is not already sufficiently described, the alt text + passed should help non-visual users understand the purpose of the + image within the context of the overall page. See{' '} Date: Tue, 19 Jul 2022 13:33:22 -0700 Subject: [PATCH 43/48] Fix certain styles responding incorrectly when `isFullScreen` is true - customSize styles should not be lost when going into fullscreen mode; the fullscreen image should simply not have said styles - isFullScreen is always true when the fullscreen wrapper is being used, and vice versa for the non-fullscreen wrapper --- src/components/image/image.tsx | 22 ++++++++++++------- .../image/image_fullscreen_wrapper.tsx | 5 ++--- src/components/image/image_types.ts | 3 +-- src/components/image/image_wrapper.tsx | 2 -- 4 files changed, 17 insertions(+), 15 deletions(-) diff --git a/src/components/image/image.tsx b/src/components/image/image.tsx index 3fe4d48a904..b4f9f739669 100644 --- a/src/components/image/image.tsx +++ b/src/components/image/image.tsx @@ -54,11 +54,9 @@ export const EuiImage: FunctionComponent = ({ const cssIsFullScreenStyles = [styles.euiImage, styles.isFullScreen]; - const isCustomSize = !isFullScreen && !isNamedSize && size !== 'original'; - + const isCustomSize = !isNamedSize && size !== 'original'; const customSize = typeof size === 'string' ? size : `${size}px`; - - const imageStyle = isCustomSize + const imageStyleWithCustomSize = isCustomSize ? { ...style, maxWidth: customSize, @@ -71,7 +69,6 @@ export const EuiImage: FunctionComponent = ({ const commonWrapperProps = { hasShadow, wrapperProps, - isFullScreen, setIsFullScreen, fullScreenIconColor, isFullWidth, @@ -84,7 +81,6 @@ export const EuiImage: FunctionComponent = ({ const commonImgProps = { className: classes, - style: imageStyle, src: src || url, ...rest, }; @@ -92,12 +88,22 @@ export const EuiImage: FunctionComponent = ({ return ( <> - {alt} + {alt} {allowFullScreen && isFullScreen && ( - {alt} + {alt} )} diff --git a/src/components/image/image_fullscreen_wrapper.tsx b/src/components/image/image_fullscreen_wrapper.tsx index 8a8c0755d35..7d2887aa063 100644 --- a/src/components/image/image_fullscreen_wrapper.tsx +++ b/src/components/image/image_fullscreen_wrapper.tsx @@ -28,7 +28,6 @@ export const EuiImageFullScreenWrapper: FunctionComponent hasShadow, caption, children, - isFullScreen, setIsFullScreen, wrapperProps, isFullWidth, @@ -84,7 +83,7 @@ export const EuiImageFullScreenWrapper: FunctionComponent onClick={closeFullScreen} onKeyDown={onKeyDown} data-test-subj="deactivateFullScreenButton" - isFullScreen={isFullScreen} + isFullScreen={true} isFullWidth={isFullWidth} fullScreenIconColor={fullScreenIconColor} > @@ -93,7 +92,7 @@ export const EuiImageFullScreenWrapper: FunctionComponent

{/* Must be outside the `figure` element in order to escape the translateY transition. see https://www.w3.org/TR/css-transforms-1/#transform-rendering */} diff --git a/src/components/image/image_types.ts b/src/components/image/image_types.ts index 9f904af5c6b..14c41374dde 100644 --- a/src/components/image/image_types.ts +++ b/src/components/image/image_types.ts @@ -93,7 +93,6 @@ export type EuiImageWrapperProps = Pick< | 'allowFullScreen' > & { isFullWidth: boolean; - isFullScreen: boolean; setIsFullScreen: (isFullScreen: boolean) => void; }; @@ -105,7 +104,7 @@ export type EuiImageButtonProps = Pick< onClick: () => void; onKeyDown?: (e: React.KeyboardEvent) => void; isFullWidth: boolean; - isFullScreen: boolean; + isFullScreen?: boolean; }; export type EuiImageCaptionProps = Pick & { diff --git a/src/components/image/image_wrapper.tsx b/src/components/image/image_wrapper.tsx index dbbedb1109e..a2a351f6718 100644 --- a/src/components/image/image_wrapper.tsx +++ b/src/components/image/image_wrapper.tsx @@ -26,7 +26,6 @@ export const EuiImageWrapper: FunctionComponent = ({ float, margin, children, - isFullScreen, setIsFullScreen, wrapperProps, fullScreenIconColor, @@ -68,7 +67,6 @@ export const EuiImageWrapper: FunctionComponent = ({ hasShadow={hasShadow} onClick={openFullScreen} data-test-subj="activateFullScreenButton" - isFullScreen={isFullScreen} isFullWidth={isFullWidth} fullScreenIconColor={fullScreenIconColor} > From b8f6f672bcd90bc1912aa284672d6b0ffac092d3 Mon Sep 17 00:00:00 2001 From: Constance Chen Date: Tue, 19 Jul 2022 13:34:38 -0700 Subject: [PATCH 44/48] [PR feedback] Include a chart/diagram example + alt text in demos + fix link color styling in fullscreen mode --- src-docs/src/views/image/image_zoom.tsx | 23 +++++++++++++++----- src/components/image/image_caption.styles.ts | 4 ++++ 2 files changed, 21 insertions(+), 6 deletions(-) diff --git a/src-docs/src/views/image/image_zoom.tsx b/src-docs/src/views/image/image_zoom.tsx index 10ef51c25fc..c50099982ad 100644 --- a/src-docs/src/views/image/image_zoom.tsx +++ b/src-docs/src/views/image/image_zoom.tsx @@ -4,10 +4,11 @@ import { EuiImage, EuiFlexGroup, EuiFlexItem, + EuiLink, } from '../../../../src/components'; export default () => ( - + ( + Browser usage on{' '} + + Wikimedia (CC BY 3.0) + + + } + alt="Pie chart describing browser usage on Wikimedia on October 2011. Internet Explorer occupies 34 percent, Firefox occupies 23 percent, Chrome occupies 20 percent, Safari occupies 11 percent, Opera occupies 5%, Android occupies 1.9 percent, and other browsers occupy 3.5 percent." + src="https://upload.wikimedia.org/wikipedia/commons/a/a2/Wikimedia_browser_share_pie_chart.png" fullScreenIconColor="dark" - src="https://upload.wikimedia.org/wikipedia/commons/a/a3/Marie_Sklodowska%2C_%C3%A9tudiante%2C_en_1895.jpg" + size={300} + style={{ padding: '20px 30px', background: 'white' }} /> diff --git a/src/components/image/image_caption.styles.ts b/src/components/image/image_caption.styles.ts index 87ce8bc1344..89a08203321 100644 --- a/src/components/image/image_caption.styles.ts +++ b/src/components/image/image_caption.styles.ts @@ -22,6 +22,10 @@ export const euiImageCaptionStyles = (euiThemeContext: UseEuiTheme) => { isOnOverlayMask: css` color: ${euiTheme.colors.ghost}; text-shadow: 0 1px 2px ${transparentize(euiTheme.colors.ink, 0.6)}; + + [class*='euiLink'] { + color: ${euiTheme.colors.ghost}; // Override link color for visibility + } `, }; }; From 04a16952c4ae579d84bb0e2e687ba52c66b3e464 Mon Sep 17 00:00:00 2001 From: Constance Chen Date: Tue, 19 Jul 2022 13:34:57 -0700 Subject: [PATCH 45/48] [Misc] Fix incorrect viewport unit being used for max-width --- src/components/image/image.styles.ts | 2 +- src/components/image/image_fullscreen_wrapper.styles.ts | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/src/components/image/image.styles.ts b/src/components/image/image.styles.ts index 20dd08bee12..8bff84141b8 100644 --- a/src/components/image/image.styles.ts +++ b/src/components/image/image.styles.ts @@ -26,7 +26,7 @@ export const euiImageStyles = (euiThemeContext: UseEuiTheme) => ({ isFullScreen: css` position: relative; ${logicalCSS('max-height', '80vh')}; - ${logicalCSS('max-width', '80vh')}; + ${logicalCSS('max-width', '80vw')}; `, hasShadow: css` ${euiShadow(euiThemeContext, 's')}; diff --git a/src/components/image/image_fullscreen_wrapper.styles.ts b/src/components/image/image_fullscreen_wrapper.styles.ts index 69fc9e6033e..9230791dbd1 100644 --- a/src/components/image/image_fullscreen_wrapper.styles.ts +++ b/src/components/image/image_fullscreen_wrapper.styles.ts @@ -23,7 +23,7 @@ export const euiImageFullscreenWrapperStyles = ( // Base euiImageFullscreenWrapper: css` ${logicalCSS('max-height', '80vh')}; - ${logicalCSS('max-width', '80vh')}; + ${logicalCSS('max-width', '80vw')}; ${logicalTextAlignCSS('center')}; // Aligns both caption and image line-height: 0; // Fixes cropping when image is resized by forcing its height to be determined by the image not line-height From 55b8028d3c13dd924b1e19a29d3175a102b191b9 Mon Sep 17 00:00:00 2001 From: Constance Chen Date: Tue, 19 Jul 2022 13:52:10 -0700 Subject: [PATCH 46/48] [cleanup] remove unnecesssary `isFullScreen` conditional - open/close icons & their styles are declared separately, so no need for conditional logic --- src/components/image/image_button.tsx | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/src/components/image/image_button.tsx b/src/components/image/image_button.tsx index 796cc4caf54..304c4ae2197 100644 --- a/src/components/image/image_button.tsx +++ b/src/components/image/image_button.tsx @@ -53,8 +53,7 @@ export const EuiImageButton: FunctionComponent = ({ const iconStyles = euiImageButtonIconStyles(euiTheme); const cssIconStyles = [ iconStyles.euiImageButton__icon, - !isFullScreen && iconStyles.openFullScreen, - isFullScreen && iconStyles.closeFullScreen, + iconStyles.openFullScreen, ]; const openFullScreenInstructions = useEuiI18n( From c1b4c571b0b55ffa32373453ad467bf0979f5869 Mon Sep 17 00:00:00 2001 From: Constance Date: Wed, 20 Jul 2022 08:34:11 -0700 Subject: [PATCH 47/48] Adding accessibility improvements to changelog --- upcoming_changelogs/5969.md | 1 + 1 file changed, 1 insertion(+) diff --git a/upcoming_changelogs/5969.md b/upcoming_changelogs/5969.md index f47e77683fa..0386ae93383 100644 --- a/upcoming_changelogs/5969.md +++ b/upcoming_changelogs/5969.md @@ -1,4 +1,5 @@ - Updated `EuiText.img` styles to prevent images from growing full width +- Improved `EuiImage`'s `allowFullScreen` screen reader experience - Updated `EuiImage`'s full screen mode to use the `fullScreenExit` icon **CSS-in-JS** From 2e983ca6444822e17a60fa5e73a7f6573091a174 Mon Sep 17 00:00:00 2001 From: Constance Chen Date: Wed, 20 Jul 2022 08:58:22 -0700 Subject: [PATCH 48/48] Add float fallbacks for browsers that don't yet support logical properties - Chrome, Opera, Edge appear to be the primary culprits (without a flag enabled) --- src/components/image/image_wrapper.styles.ts | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/src/components/image/image_wrapper.styles.ts b/src/components/image/image_wrapper.styles.ts index 5151ddd1913..4c68d66da93 100644 --- a/src/components/image/image_wrapper.styles.ts +++ b/src/components/image/image_wrapper.styles.ts @@ -45,8 +45,11 @@ export const euiImageWrapperStyles = (euiThemeContext: UseEuiTheme) => { margin: ${euiTheme.size.xl}; `, // Floats + // 1: Logical properties/values in `float` is currently not yet supported by all browsers w/o flags + // @see https://caniuse.com/mdn-css_properties_float_flow_relative_values for when we can remove left/right fallbacks left: css` @media only screen and (min-width: ${euiTheme.breakpoint.m}px) { + float: left; /* 1 */ float: ${logicalSide.left}; ${logicalCSS('margin-left', '0')}; ${logicalCSS('margin-top', '0')}; @@ -54,6 +57,7 @@ export const euiImageWrapperStyles = (euiThemeContext: UseEuiTheme) => { `, right: css` @media only screen and (min-width: ${euiTheme.breakpoint.m}px) { + float: right; /* 1 */ float: ${logicalSide.right}; ${logicalCSS('margin-right', '0')}; ${logicalCSS('margin-top', '0')};