2323 */
2424
2525import { useStyle } from '@instructure/emotion'
26- import { useState , SyntheticEvent , useEffect } from 'react'
26+ import {
27+ useState ,
28+ SyntheticEvent ,
29+ useEffect ,
30+ forwardRef ,
31+ ForwardedRef
32+ } from 'react'
2733
2834import { View } from '@instructure/ui-view'
2935import { callRenderProp , passthroughProps } from '@instructure/ui-react-utils'
@@ -37,125 +43,130 @@ import generateComponentTheme from './theme'
3743category: components
3844---
3945**/
46+ const Avatar = forwardRef (
47+ (
48+ {
49+ size = 'medium' ,
50+ color = 'default' ,
51+ hasInverseColor = false ,
52+ showBorder = 'auto' ,
53+ shape = 'circle' ,
54+ display = 'inline-block' ,
55+ onImageLoaded = ( _event : SyntheticEvent ) => { } ,
56+ src,
57+ name,
58+ renderIcon,
59+ alt,
60+ as,
61+ margin,
62+ themeOverride,
63+ elementRef,
64+ ...rest
65+ } : AvatarProps ,
66+ ref : ForwardedRef < View >
67+ ) => {
68+ const [ loaded , setLoaded ] = useState ( false )
4069
41- const Avatar = ( {
42- size = 'medium' ,
43- color = 'default' ,
44- hasInverseColor = false ,
45- showBorder = 'auto' ,
46- shape = 'circle' ,
47- display = 'inline-block' ,
48- onImageLoaded = ( _event : SyntheticEvent ) => { } ,
49- src,
50- name,
51- renderIcon,
52- alt,
53- as,
54- margin,
55- themeOverride,
56- elementRef,
57- ...rest
58- } : AvatarProps ) => {
59- const [ loaded , setLoaded ] = useState ( false )
70+ const styles = useStyle ( {
71+ generateStyle,
72+ generateComponentTheme,
73+ params : {
74+ loaded,
75+ size,
76+ color,
77+ hasInverseColor,
78+ shape,
79+ src,
80+ showBorder,
81+ themeOverride
82+ } ,
83+ componentId : 'Avatar' ,
84+ displayName : 'Avatar'
85+ } )
6086
61- const styles = useStyle ( {
62- generateStyle,
63- generateComponentTheme,
64- params : {
65- loaded,
66- size,
67- color,
68- hasInverseColor,
69- shape,
70- src,
71- showBorder,
72- themeOverride
73- } ,
74- componentId : 'Avatar' ,
75- displayName : 'Avatar'
76- } )
87+ useEffect ( ( ) => {
88+ // in case the image is unset in an update, show icons/initials again
89+ if ( loaded && ! src ) {
90+ setLoaded ( false )
91+ }
92+ } , [ loaded , src ] )
7793
78- useEffect ( ( ) => {
79- // in case the image is unset in an update, show icons/initials again
80- if ( loaded && ! src ) {
81- setLoaded ( false )
82- }
83- } , [ loaded , src ] )
94+ const makeInitialsFromName = ( ) => {
95+ if ( ! name || typeof name !== 'string' ) {
96+ return
97+ }
98+ const currentName = name . trim ( )
99+ if ( currentName . length === 0 ) {
100+ return
101+ }
84102
85- const makeInitialsFromName = ( ) => {
86- if ( ! name || typeof name !== 'string' ) {
87- return
103+ if ( currentName . match ( / \s + / ) ) {
104+ const names = currentName . split ( / \s + / )
105+ return ( names [ 0 ] [ 0 ] + names [ names . length - 1 ] [ 0 ] ) . toUpperCase ( )
106+ } else {
107+ return currentName [ 0 ] . toUpperCase ( )
108+ }
88109 }
89- const currentName = name . trim ( )
90- if ( currentName . length === 0 ) {
91- return
110+
111+ const handleImageLoaded = ( event : SyntheticEvent ) => {
112+ setLoaded ( true )
113+ onImageLoaded ( event )
92114 }
93115
94- if ( currentName . match ( / \s + / ) ) {
95- const names = currentName . split ( / \s + / )
96- return ( names [ 0 ] [ 0 ] + names [ names . length - 1 ] [ 0 ] ) . toUpperCase ( )
97- } else {
98- return currentName [ 0 ] . toUpperCase ( )
116+ const renderInitials = ( ) => {
117+ return (
118+ < span css = { styles ?. initials } aria-hidden = "true" >
119+ { makeInitialsFromName ( ) }
120+ </ span >
121+ )
99122 }
100- }
101123
102- const handleImageLoaded = ( event : SyntheticEvent ) => {
103- setLoaded ( true )
104- onImageLoaded ( event )
105- }
124+ const renderContent = ( ) => {
125+ if ( ! renderIcon ) {
126+ return renderInitials ( )
127+ }
106128
107- const renderInitials = ( ) => {
108- return (
109- < span css = { styles ?. initials } aria-hidden = "true" >
110- { makeInitialsFromName ( ) }
111- </ span >
112- )
113- }
114-
115- const renderContent = ( ) => {
116- if ( ! renderIcon ) {
117- return renderInitials ( )
129+ return < span css = { styles ?. iconSVG } > { callRenderProp ( renderIcon ) } </ span >
118130 }
119131
120- return < span css = { styles ?. iconSVG } > { callRenderProp ( renderIcon ) } </ span >
132+ return (
133+ < View
134+ { ...passthroughProps ( {
135+ size,
136+ color,
137+ hasInverseColor,
138+ showBorder,
139+ shape,
140+ display,
141+ src,
142+ name,
143+ renderIcon,
144+ alt,
145+ as,
146+ margin,
147+ ...rest
148+ } ) }
149+ ref = { ref }
150+ aria-label = { alt ? alt : undefined }
151+ role = { alt ? 'img' : undefined }
152+ as = { as }
153+ elementRef = { elementRef }
154+ margin = { margin }
155+ css = { styles ?. avatar }
156+ display = { display }
157+ >
158+ < img // This is visually hidden and is here for loading purposes only
159+ src = { src }
160+ css = { styles ?. loadImage }
161+ alt = { alt }
162+ onLoad = { handleImageLoaded }
163+ aria-hidden = "true"
164+ />
165+ { ! loaded && renderContent ( ) }
166+ </ View >
167+ )
121168 }
122-
123- return (
124- < View
125- { ...passthroughProps ( {
126- size,
127- color,
128- hasInverseColor,
129- showBorder,
130- shape,
131- display,
132- src,
133- name,
134- renderIcon,
135- alt,
136- as,
137- margin,
138- ...rest
139- } ) }
140- aria-label = { alt ? alt : undefined }
141- role = { alt ? 'img' : undefined }
142- as = { as }
143- elementRef = { elementRef }
144- margin = { margin }
145- css = { styles ?. avatar }
146- display = { display }
147- >
148- < img // This is visually hidden and is here for loading purposes only
149- src = { src }
150- css = { styles ?. loadImage }
151- alt = { alt }
152- onLoad = { handleImageLoaded }
153- aria-hidden = "true"
154- />
155- { ! loaded && renderContent ( ) }
156- </ View >
157- )
158- }
169+ )
159170
160171export default Avatar
161172export { Avatar }
0 commit comments