diff --git a/CHANGELOG.md b/CHANGELOG.md index 4c56c74555..2c6f30ccca 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -26,6 +26,7 @@ This project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.htm - Add `shadowLevel1Dark` in Teams themes @notandrew ([#1887](https://github.com/stardust-ui/react/pull/1887)) - When merging themes use deep merge for site and component variables @miroslavstastny ([#1907](https://github.com/stardust-ui/react/pull/1907)) - Fix `Dropdown` to properly accept `id` on `searchInput` @silviuavram ([#1938](https://github.com/stardust-ui/react/pull/1938)) +- Fix white flash when activating `Embed` component @lucivpav ([#1909](https://github.com/stardust-ui/react/pull/1909)) ### Features - Add `TextArea` component @lucivpav ([#1897](https://github.com/stardust-ui/react/pull/1897)) diff --git a/packages/react/src/components/Embed/Embed.tsx b/packages/react/src/components/Embed/Embed.tsx index 2e6eeea69c..9cfd5baf15 100644 --- a/packages/react/src/components/Embed/Embed.tsx +++ b/packages/react/src/components/Embed/Embed.tsx @@ -14,10 +14,11 @@ import { import { embedBehavior } from '../../lib/accessibility' import { Accessibility } from '../../lib/accessibility/types' import Icon, { IconProps } from '../Icon/Icon' -import Image, { ImageProps } from '../Image/Image' +import Image from '../Image/Image' import Video, { VideoProps } from '../Video/Video' import Box, { BoxProps } from '../Box/Box' import { ComponentEventHandler, WithAsProp, ShorthandValue, withSafeTypeForAs } from '../../types' +import { Ref } from '@stardust-ui/react-component-ref' export interface EmbedSlotClassNames { control: string @@ -55,7 +56,7 @@ export interface EmbedProps extends UIComponentProps { onClick?: ComponentEventHandler /** Image source URL for when video isn't playing. */ - placeholder?: ShorthandValue + placeholder?: string /** Shorthand for an embedded video. */ video?: ShorthandValue @@ -63,6 +64,7 @@ export interface EmbedProps extends UIComponentProps { export interface EmbedState { active: boolean + iframeLoaded: boolean } class Embed extends AutoControlledComponent, EmbedState> { @@ -80,11 +82,17 @@ class Embed extends AutoControlledComponent, EmbedState> active: PropTypes.bool, defaultActive: PropTypes.bool, control: customPropTypes.itemShorthand, - iframe: customPropTypes.itemShorthand, + iframe: customPropTypes.every([ + customPropTypes.disallow(['video']), + customPropTypes.itemShorthand, + ]), onActiveChanged: PropTypes.func, onClick: PropTypes.func, placeholder: PropTypes.string, - video: customPropTypes.itemShorthand, + video: customPropTypes.every([ + customPropTypes.disallow(['iframe']), + customPropTypes.itemShorthand, + ]), } static defaultProps = { @@ -103,8 +111,10 @@ class Embed extends AutoControlledComponent, EmbedState> performClick: event => this.handleClick(event), } + frameRef = React.createRef() + getInitialAutoControlledState(): EmbedState { - return { active: false } + return { active: false, iframeLoaded: false } } handleClick = e => { @@ -121,10 +131,31 @@ class Embed extends AutoControlledComponent, EmbedState> _.invoke(this.props, 'onClick', e, { ...this.props, active: !this.state.active }) } + handleFrameOverrides = predefinedProps => ({ + onLoad: (e: React.SyntheticEvent) => { + _.invoke(predefinedProps, 'onLoad', e) + + this.setState({ iframeLoaded: true }) + this.frameRef.current.contentWindow.focus() + }, + }) + renderComponent({ ElementType, classes, accessibility, unhandledProps, styles, variables }) { const { control, iframe, placeholder, video } = this.props - const { active } = this.state - const controlVisible = !_.isNil(video) || !active + const { active, iframeLoaded } = this.state + + const placeholderElement = placeholder ? ( + + ) : null + + const hasIframe = !_.isNil(iframe) + const hasVideo = !_.isNil(video) + const controlVisible = !active || hasVideo + const placeholderVisible = !active || (hasIframe && active && !iframeLoaded) return ( , EmbedState> {...unhandledProps} {...applyAccessibilityKeyHandlers(accessibility.keyHandlers.root, unhandledProps)} > - {active ? ( + {active && ( <> {Video.create(video, { defaultProps: { @@ -142,6 +173,7 @@ class Embed extends AutoControlledComponent, EmbedState> controls: false, loop: true, muted: true, + poster: placeholder, styles: styles.video, variables: { width: variables.width, @@ -149,20 +181,21 @@ class Embed extends AutoControlledComponent, EmbedState> }, }, })} - {Box.create(iframe, { defaultProps: { as: 'iframe', styles: styles.iframe } })} + {iframe && ( + + {Box.create(iframe, { + defaultProps: { + as: 'iframe', + styles: styles.iframe, + }, + overrideProps: this.handleFrameOverrides, + })} + + )} - ) : ( - Image.create(placeholder, { - defaultProps: { - styles: styles.image, - variables: { - width: variables.width, - height: variables.height, - }, - }, - }) )} + {placeholderVisible && placeholderElement} {controlVisible && Icon.create(control, { defaultProps: { diff --git a/packages/react/src/themes/teams/components/Embed/embedStyles.ts b/packages/react/src/themes/teams/components/Embed/embedStyles.ts index 7f41874ab1..3efff54bf4 100644 --- a/packages/react/src/themes/teams/components/Embed/embedStyles.ts +++ b/packages/react/src/themes/teams/components/Embed/embedStyles.ts @@ -48,7 +48,8 @@ export default { top: '50%', transform: 'translate(-50%, -50%)', }), - iframe: (): ICSSInJSStyle => ({ + iframe: ({ props: p }): ICSSInJSStyle => ({ display: 'block', + ...(!p.iframeLoaded && { display: 'none' }), }), } as ComponentSlotStylesPrepared