@atlaskit/avatar
Advanced tools
Comparing version 20.5.10 to 21.0.0
# @atlaskit/avatar | ||
## 21.0.0 | ||
### Major Changes | ||
- [`92bb02bc46b`](https://bitbucket.org/atlassian/atlassian-frontend/commits/92bb02bc46b) - [ux] There are **no code changes required** to consume this major, but you should be aware that internal changes have been made to how `@atlaskit/avatar` loads images. | ||
Before, the image loading behaviour was written in JS. Now, it leans on a standard HTML `img` tag if you provide a `src` prop, allowing it to rely on the browser to optimise the loading. These changes should result in faster image loading and an improved server-side rendering story. | ||
In this version, the **breaking change is that you will no longer see a fallback icon while the image is loading**. We have intentionally removed this loading behaviour as it is no longer consistent with our native `img` behaviour-first approach, and was a source of SSR bugs. Avatar images will load either instantly from the cache, or very fast from a CDN. In the edge cases where there is an error with the image src provided, we will still fall back to a default icon. | ||
### Patch Changes | ||
- [`be23a6d8ee1`](https://bitbucket.org/atlassian/atlassian-frontend/commits/be23a6d8ee1) - [ux] Updates color tokens used to be more semantically accurate | ||
## 20.5.10 | ||
@@ -4,0 +18,0 @@ |
@@ -41,3 +41,3 @@ "use strict"; | ||
var packageName = "@atlaskit/avatar"; | ||
var packageVersion = "20.5.10"; | ||
var packageVersion = "21.0.0"; | ||
@@ -44,0 +44,0 @@ var getStyles = function getStyles(css, _ref) { |
@@ -26,7 +26,7 @@ "use strict"; | ||
// eslint-disable-next-line @repo/internal/fs/filename-pattern-match | ||
var ICON_BACKGROUND = "var(--ds-text-inverse, ".concat((0, _colors.background)(), ")"); | ||
var ICON_BACKGROUND = "var(--ds-icon-inverse, ".concat((0, _colors.background)(), ")"); | ||
exports.ICON_BACKGROUND = ICON_BACKGROUND; | ||
var ICON_COLOR = "var(--ds-text-subtlest, ".concat(_colors.N90, ")"); | ||
var ICON_COLOR = "var(--ds-icon-subtle, ".concat(_colors.N90, ")"); | ||
exports.ICON_COLOR = ICON_COLOR; | ||
var avatarImageStyles = (0, _core.css)({ | ||
var avatarDefaultIconStyles = (0, _core.css)({ | ||
display: 'block', | ||
@@ -37,11 +37,7 @@ width: '100%', | ||
}); | ||
var loadingImageStyles = (0, _core.css)({ | ||
var avatarImageStyles = (0, _core.css)({ | ||
display: 'flex', | ||
width: '100%', | ||
height: '100%', | ||
flex: '1 1 100%', | ||
backgroundColor: 'transparent', | ||
backgroundPosition: 'center', | ||
backgroundRepeat: 'no-repeat', | ||
backgroundSize: 'cover' | ||
flex: '1 1 100%' | ||
}); | ||
@@ -62,43 +58,16 @@ /** | ||
var _useState = (0, _react.useState)('initial'), | ||
var _useState = (0, _react.useState)(false), | ||
_useState2 = (0, _slicedToArray2.default)(_useState, 2), | ||
phase = _useState2[0], | ||
setPhase = _useState2[1]; | ||
hasImageErrored = _useState2[0], | ||
setHasImageErrored = _useState2[1]; | ||
var borderRadius = appearance === 'circle' ? '50%' : "".concat(_constants.AVATAR_RADIUS[size], "px"); | ||
var image = (0, _react.useMemo)(function () { | ||
if (src) { | ||
setPhase('loading'); | ||
var img = new Image(); | ||
var borderRadius = appearance === 'circle' ? '50%' : "".concat(_constants.AVATAR_RADIUS[size], "px"); // If src changes, reset state | ||
img.onload = function () { | ||
return setPhase('loaded'); | ||
}; | ||
img.onerror = function () { | ||
return setPhase('error'); | ||
}; | ||
img.src = src; | ||
return img; | ||
} | ||
return null; | ||
(0, _react.useEffect)(function () { | ||
setHasImageErrored(false); | ||
}, [src]); | ||
(0, _react.useEffect)(function () { | ||
return function () { | ||
if (image) { | ||
image.onload = function () {}; | ||
image.onerror = function () {}; | ||
} | ||
}; | ||
}, [image]); | ||
var imageHasLoadedAsync = src && phase !== 'loading' && phase !== 'error'; | ||
var imageHasLoadedSync = src && phase === 'loading' && (image === null || image === void 0 ? void 0 : image.complete); | ||
var imageHasLoaded = imageHasLoadedAsync || imageHasLoadedSync; | ||
if (!imageHasLoaded) { | ||
if (!src || hasImageErrored) { | ||
return (0, _core.jsx)("span", { | ||
css: [avatarImageStyles, // TODO: These dynamic SVG styles can't be set in 'style'. On a refactor, use a css custom property to pass down the size | ||
css: [avatarDefaultIconStyles, // TODO: These dynamic SVG styles can't be set in 'style'. On a refactor, use a css custom property to pass down the size | ||
// eslint-disable-next-line @repo/internal/react/consistent-css-prop-usage | ||
@@ -124,11 +93,13 @@ { | ||
return (0, _core.jsx)("span", { | ||
css: loadingImageStyles, | ||
return (0, _core.jsx)("img", { | ||
src: src, | ||
alt: alt, | ||
"data-testid": testId && "".concat(testId, "--image"), | ||
css: avatarImageStyles, | ||
style: { | ||
backgroundImage: "url(\"".concat(src, "\")"), | ||
borderRadius: borderRadius | ||
}, | ||
role: alt ? 'img' : undefined, | ||
"aria-label": alt || undefined, | ||
"data-testid": testId && "".concat(testId, "--image") | ||
onError: function onError() { | ||
return setHasImageErrored(true); | ||
} | ||
}); | ||
@@ -135,0 +106,0 @@ }; |
{ | ||
"name": "@atlaskit/avatar", | ||
"version": "20.5.10", | ||
"version": "21.0.0", | ||
"sideEffects": false | ||
} |
@@ -13,3 +13,3 @@ /** @jsx jsx */ | ||
const packageName = "@atlaskit/avatar"; | ||
const packageVersion = "20.5.10"; | ||
const packageVersion = "21.0.0"; | ||
@@ -16,0 +16,0 @@ const getStyles = (css, { |
/** @jsx jsx */ | ||
// eslint-disable-next-line @repo/internal/fs/filename-pattern-match | ||
import { useEffect, useMemo, useState } from 'react'; | ||
import { useEffect, useState } from 'react'; | ||
import { css, jsx } from '@emotion/core'; | ||
@@ -9,5 +9,5 @@ import PersonIcon from '@atlaskit/icon/glyph/person'; | ||
import { AVATAR_RADIUS, AVATAR_SIZES } from './constants'; | ||
export const ICON_BACKGROUND = `var(--ds-text-inverse, ${background()})`; | ||
export const ICON_COLOR = `var(--ds-text-subtlest, ${N90})`; | ||
const avatarImageStyles = css({ | ||
export const ICON_BACKGROUND = `var(--ds-icon-inverse, ${background()})`; | ||
export const ICON_COLOR = `var(--ds-icon-subtle, ${N90})`; | ||
const avatarDefaultIconStyles = css({ | ||
display: 'block', | ||
@@ -18,11 +18,7 @@ width: '100%', | ||
}); | ||
const loadingImageStyles = css({ | ||
const avatarImageStyles = css({ | ||
display: 'flex', | ||
width: '100%', | ||
height: '100%', | ||
flex: '1 1 100%', | ||
backgroundColor: 'transparent', | ||
backgroundPosition: 'center', | ||
backgroundRepeat: 'no-repeat', | ||
backgroundSize: 'cover' | ||
flex: '1 1 100%' | ||
}); | ||
@@ -42,35 +38,12 @@ /** | ||
}) => { | ||
const [phase, setPhase] = useState('initial'); | ||
const borderRadius = appearance === 'circle' ? '50%' : `${AVATAR_RADIUS[size]}px`; | ||
const image = useMemo(() => { | ||
if (src) { | ||
setPhase('loading'); | ||
const img = new Image(); | ||
const [hasImageErrored, setHasImageErrored] = useState(false); | ||
const borderRadius = appearance === 'circle' ? '50%' : `${AVATAR_RADIUS[size]}px`; // If src changes, reset state | ||
img.onload = () => setPhase('loaded'); | ||
img.onerror = () => setPhase('error'); | ||
img.src = src; | ||
return img; | ||
} | ||
return null; | ||
useEffect(() => { | ||
setHasImageErrored(false); | ||
}, [src]); | ||
useEffect(() => { | ||
return () => { | ||
if (image) { | ||
image.onload = () => {}; | ||
image.onerror = () => {}; | ||
} | ||
}; | ||
}, [image]); | ||
const imageHasLoadedAsync = src && phase !== 'loading' && phase !== 'error'; | ||
const imageHasLoadedSync = src && phase === 'loading' && (image === null || image === void 0 ? void 0 : image.complete); | ||
const imageHasLoaded = imageHasLoadedAsync || imageHasLoadedSync; | ||
if (!imageHasLoaded) { | ||
if (!src || hasImageErrored) { | ||
return jsx("span", { | ||
css: [avatarImageStyles, // TODO: These dynamic SVG styles can't be set in 'style'. On a refactor, use a css custom property to pass down the size | ||
css: [avatarDefaultIconStyles, // TODO: These dynamic SVG styles can't be set in 'style'. On a refactor, use a css custom property to pass down the size | ||
// eslint-disable-next-line @repo/internal/react/consistent-css-prop-usage | ||
@@ -96,11 +69,11 @@ { | ||
return jsx("span", { | ||
css: loadingImageStyles, | ||
return jsx("img", { | ||
src: src, | ||
alt: alt, | ||
"data-testid": testId && `${testId}--image`, | ||
css: avatarImageStyles, | ||
style: { | ||
backgroundImage: `url("${src}")`, | ||
borderRadius: borderRadius | ||
}, | ||
role: alt ? 'img' : undefined, | ||
"aria-label": alt || undefined, | ||
"data-testid": testId && `${testId}--image` | ||
onError: () => setHasImageErrored(true) | ||
}); | ||
@@ -107,0 +80,0 @@ }; |
{ | ||
"name": "@atlaskit/avatar", | ||
"version": "20.5.10", | ||
"version": "21.0.0", | ||
"sideEffects": false | ||
} |
@@ -23,3 +23,3 @@ import _defineProperty from "@babel/runtime/helpers/defineProperty"; | ||
var packageName = "@atlaskit/avatar"; | ||
var packageVersion = "20.5.10"; | ||
var packageVersion = "21.0.0"; | ||
@@ -26,0 +26,0 @@ var getStyles = function getStyles(css, _ref) { |
@@ -5,3 +5,3 @@ import _slicedToArray from "@babel/runtime/helpers/slicedToArray"; | ||
// eslint-disable-next-line @repo/internal/fs/filename-pattern-match | ||
import { useEffect, useMemo, useState } from 'react'; | ||
import { useEffect, useState } from 'react'; | ||
import { css, jsx } from '@emotion/core'; | ||
@@ -12,5 +12,5 @@ import PersonIcon from '@atlaskit/icon/glyph/person'; | ||
import { AVATAR_RADIUS, AVATAR_SIZES } from './constants'; | ||
export var ICON_BACKGROUND = "var(--ds-text-inverse, ".concat(background(), ")"); | ||
export var ICON_COLOR = "var(--ds-text-subtlest, ".concat(N90, ")"); | ||
var avatarImageStyles = css({ | ||
export var ICON_BACKGROUND = "var(--ds-icon-inverse, ".concat(background(), ")"); | ||
export var ICON_COLOR = "var(--ds-icon-subtle, ".concat(N90, ")"); | ||
var avatarDefaultIconStyles = css({ | ||
display: 'block', | ||
@@ -21,11 +21,7 @@ width: '100%', | ||
}); | ||
var loadingImageStyles = css({ | ||
var avatarImageStyles = css({ | ||
display: 'flex', | ||
width: '100%', | ||
height: '100%', | ||
flex: '1 1 100%', | ||
backgroundColor: 'transparent', | ||
backgroundPosition: 'center', | ||
backgroundRepeat: 'no-repeat', | ||
backgroundSize: 'cover' | ||
flex: '1 1 100%' | ||
}); | ||
@@ -46,43 +42,16 @@ /** | ||
var _useState = useState('initial'), | ||
var _useState = useState(false), | ||
_useState2 = _slicedToArray(_useState, 2), | ||
phase = _useState2[0], | ||
setPhase = _useState2[1]; | ||
hasImageErrored = _useState2[0], | ||
setHasImageErrored = _useState2[1]; | ||
var borderRadius = appearance === 'circle' ? '50%' : "".concat(AVATAR_RADIUS[size], "px"); | ||
var image = useMemo(function () { | ||
if (src) { | ||
setPhase('loading'); | ||
var img = new Image(); | ||
var borderRadius = appearance === 'circle' ? '50%' : "".concat(AVATAR_RADIUS[size], "px"); // If src changes, reset state | ||
img.onload = function () { | ||
return setPhase('loaded'); | ||
}; | ||
img.onerror = function () { | ||
return setPhase('error'); | ||
}; | ||
img.src = src; | ||
return img; | ||
} | ||
return null; | ||
useEffect(function () { | ||
setHasImageErrored(false); | ||
}, [src]); | ||
useEffect(function () { | ||
return function () { | ||
if (image) { | ||
image.onload = function () {}; | ||
image.onerror = function () {}; | ||
} | ||
}; | ||
}, [image]); | ||
var imageHasLoadedAsync = src && phase !== 'loading' && phase !== 'error'; | ||
var imageHasLoadedSync = src && phase === 'loading' && (image === null || image === void 0 ? void 0 : image.complete); | ||
var imageHasLoaded = imageHasLoadedAsync || imageHasLoadedSync; | ||
if (!imageHasLoaded) { | ||
if (!src || hasImageErrored) { | ||
return jsx("span", { | ||
css: [avatarImageStyles, // TODO: These dynamic SVG styles can't be set in 'style'. On a refactor, use a css custom property to pass down the size | ||
css: [avatarDefaultIconStyles, // TODO: These dynamic SVG styles can't be set in 'style'. On a refactor, use a css custom property to pass down the size | ||
// eslint-disable-next-line @repo/internal/react/consistent-css-prop-usage | ||
@@ -108,11 +77,13 @@ { | ||
return jsx("span", { | ||
css: loadingImageStyles, | ||
return jsx("img", { | ||
src: src, | ||
alt: alt, | ||
"data-testid": testId && "".concat(testId, "--image"), | ||
css: avatarImageStyles, | ||
style: { | ||
backgroundImage: "url(\"".concat(src, "\")"), | ||
borderRadius: borderRadius | ||
}, | ||
role: alt ? 'img' : undefined, | ||
"aria-label": alt || undefined, | ||
"data-testid": testId && "".concat(testId, "--image") | ||
onError: function onError() { | ||
return setHasImageErrored(true); | ||
} | ||
}); | ||
@@ -119,0 +90,0 @@ }; |
{ | ||
"name": "@atlaskit/avatar", | ||
"version": "20.5.10", | ||
"version": "21.0.0", | ||
"sideEffects": false | ||
} |
@@ -26,3 +26,3 @@ /** @jsx jsx */ | ||
* Used to provide better content to screen readers when using presence/status. Rather | ||
* than a screen reader speaking "online, approved, John Smith", passing in an label | ||
* than a screen reader speaking "online, approved, John Smith", passing in a label | ||
* allows a custom message like "John Smith (approved and online)". | ||
@@ -49,4 +49,3 @@ */ | ||
/** | ||
* Name will be displayed in a tooltip, also used by screen readers as fallback | ||
* content if the image fails to load. | ||
* Provides alt text for the avatar image. | ||
*/ | ||
@@ -53,0 +52,0 @@ name?: string; |
@@ -11,4 +11,4 @@ /** @jsx jsx */ | ||
} | ||
export declare const ICON_BACKGROUND: "var(--ds-text-inverse)"; | ||
export declare const ICON_COLOR: "var(--ds-text-subtlest)"; | ||
export declare const ICON_BACKGROUND: "var(--ds-icon-inverse)"; | ||
export declare const ICON_COLOR: "var(--ds-icon-subtle)"; | ||
/** | ||
@@ -15,0 +15,0 @@ * __Avatar image__ |
{ | ||
"name": "@atlaskit/avatar", | ||
"version": "20.5.10", | ||
"version": "21.0.0", | ||
"description": "An avatar is a visual representation of a user or entity.", | ||
@@ -8,3 +8,3 @@ "publishConfig": { | ||
}, | ||
"repository": "https://bitbucket.org/atlassian/atlassian-frontend", | ||
"repository": "https://bitbucket.org/atlassian/atlassian-frontend-mirror", | ||
"author": "Atlassian Pty Ltd", | ||
@@ -11,0 +11,0 @@ "license": "Apache-2.0", |
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
No repository
Supply chain riskPackage does not have a linked source code repository. Without this field, a package will have no reference to the location of the source code use to generate the package.
Found 1 instance in 1 package
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
No repository
Supply chain riskPackage does not have a linked source code repository. Without this field, a package will have no reference to the location of the source code use to generate the package.
Found 1 instance in 1 package
204059
3802