import { Accessibility, embedBehavior, EmbedBehaviorProps } from '@fluentui/accessibility'; import * as _ from 'lodash'; import * as PropTypes from 'prop-types'; import * as React from 'react'; import * as customPropTypes from '@fluentui/react-proptypes'; import { createShorthandFactory, UIComponentProps, commonPropTypes } from '../../utils'; import { Image } from '../Image/Image'; import { Video, VideoProps } from '../Video/Video'; import { Box, BoxProps } from '../Box/Box'; import { ComponentEventHandler, ShorthandValue, FluentComponentStaticProps } from '../../types'; import { Ref } from '@fluentui/react-component-ref'; import { ComponentWithAs, getElementType, useUnhandledProps, useFluentContext, useAutoControlled, useAccessibility, useTelemetry, useStyles, } from '@fluentui/react-bindings'; export interface EmbedSlotClassNames { control: string; } export interface EmbedProps extends UIComponentProps { /** Accessibility behavior if overridden by the user. */ accessibility?: Accessibility; /** Alternative text. */ alt?: string; /** Corresponds to HTML title attribute. */ title?: string; /** Whether the embedded object should be active. */ active?: boolean; /** Whether the embedded object should start active. */ defaultActive?: boolean; /** Shorthand for an control. */ control?: ShorthandValue; /** Shorthand for an embedded iframe. */ iframe?: ShorthandValue; /** * Event for request to change 'active' value. * @param event - React's original SyntheticEvent. * @param data - All props and proposed value. */ onActiveChange?: ComponentEventHandler; /** * Called when is clicked. * * @param event - React's original SyntheticEvent. * @param data - All item props. */ onClick?: ComponentEventHandler; /** Image source URL for when video isn't playing. */ placeholder?: string; /** Shorthand for an embedded video. */ video?: ShorthandValue; } export const embedClassName = 'ui-embed'; export const embedSlotClassNames: EmbedSlotClassNames = { control: `${embedClassName}__control`, }; export type EmbedStylesProps = Required> & { iframeLoaded: boolean }; /** * An Embed displays content from external websites, like a post from external social network. * * @accessibility * A `placeholder` slot represents an [`Image`](/components/image/definition) component, please follow recommendations from its * accessibility section. */ export const Embed: ComponentWithAs<'span', EmbedProps> & FluentComponentStaticProps = props => { const context = useFluentContext(); const { setStart, setEnd } = useTelemetry(Embed.displayName, context.telemetry); setStart(); const { alt, title, control, iframe, placeholder, video, variables, styles, className, design } = props; const ElementType = getElementType(props); const unhandledProps = useUnhandledProps(Embed.handledProps, props); const getA11yProps = useAccessibility(props.accessibility, { debugName: Embed.displayName, actionHandlers: { performClick: event => handleClick(event), }, mapPropsToBehavior: () => ({ alt, title, }), rtl: context.rtl, }); const [active, setACtive] = useAutoControlled({ defaultValue: props.defaultActive, value: props.active, initialValue: false, }); const [iframeLoaded, setIframeLoaded] = React.useState(false); const frameRef = React.useRef(); const handleClick = e => { e.stopPropagation(); e.preventDefault(); const iframeNil = _.isNil(props.iframe); const newActive = !active; if (iframeNil || (!iframeNil && newActive)) { setACtive(newActive); _.invoke(props, 'onActiveChange', e, { ...props, active: newActive }); } _.invoke(props, 'onClick', e, { ...props, active: newActive }); }; const handleFrameOverrides = predefinedProps => ({ onLoad: (e: React.SyntheticEvent) => { _.invoke(predefinedProps, 'onLoad', e); setIframeLoaded(true); frameRef.current.contentWindow.focus(); }, }); const { classes, styles: resolvedStyles } = useStyles(Embed.displayName, { className: embedClassName, mapPropsToStyles: () => ({ iframeLoaded, active, }), mapPropsToInlineStyles: () => ({ className, design, styles, variables, }), rtl: context.rtl, }); const placeholderElement = placeholder ? ( ) : null; const hasIframe = !_.isNil(iframe); const hasVideo = !_.isNil(video); const controlVisible = !active || hasVideo; const placeholderVisible = !active || (hasIframe && active && !iframeLoaded); const element = ( {active && ( <> {Video.create(video, { defaultProps: () => getA11yProps('video', { autoPlay: true, controls: false, loop: true, muted: true, poster: placeholder, styles: resolvedStyles.video, variables: { width: variables.width, height: variables.height, }, }), })} {iframe && ( {Box.create(iframe, { defaultProps: () => getA11yProps('iframe', { as: 'iframe', styles: resolvedStyles.iframe, }), overrideProps: handleFrameOverrides, })} )} )} {placeholderVisible && placeholderElement} {controlVisible && Box.create(control, { defaultProps: () => getA11yProps('control', { className: embedSlotClassNames.control, styles: resolvedStyles.control, }), })} ); setEnd(); return element; }; Embed.displayName = 'Embed'; Embed.propTypes = { ...commonPropTypes.createCommon({ children: false, content: false, }), active: PropTypes.bool, defaultActive: PropTypes.bool, control: customPropTypes.itemShorthand, iframe: customPropTypes.every([customPropTypes.disallow(['video']), customPropTypes.itemShorthand]), onActiveChange: PropTypes.func, onClick: PropTypes.func, placeholder: PropTypes.string, video: customPropTypes.every([customPropTypes.disallow(['iframe']), customPropTypes.itemShorthand]), }; Embed.defaultProps = { as: 'span', accessibility: embedBehavior, control: {}, }; Embed.handledProps = Object.keys(Embed.propTypes) as any; Embed.create = createShorthandFactory({ Component: Embed });