Skip to content
This repository has been archived by the owner on Mar 4, 2020. It is now read-only.

feat(Embed|Video): add components #1108

Merged
merged 32 commits into from
Apr 10, 2019
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
32 commits
Select commit Hold shift + click to select a range
f431c27
feat: implements video and video gif component
stuartlong Mar 25, 2019
ffb49f7
Responded to PR comments
stuartlong Mar 28, 2019
449c955
Create Embed component
stuartlong Apr 2, 2019
92fd5c0
Merge branch 'master' into feat/stuartlo/video
layershifter Apr 4, 2019
498865f
Merge branch 'feat/stuartlo/video' of https://github.com/stuartlong/r…
stuartlong Apr 4, 2019
f6a3110
USe AutoControlled component
stuartlong Apr 4, 2019
443f2f4
Respond to code review comments
stuartlong Apr 4, 2019
cebb462
Add some periods
stuartlong Apr 4, 2019
f762d05
Remove redundant conditional in styles
stuartlong Apr 4, 2019
fd55afc
reorder props, add missing propTypes
layershifter Apr 8, 2019
10f77dc
remove unused lodash
layershifter Apr 8, 2019
7e5128d
reorder props, add missing, remove useless
layershifter Apr 8, 2019
4885f72
use trySetState
layershifter Apr 8, 2019
2c25207
use `v` & VideoVariables
layershifter Apr 8, 2019
8667773
move control to a separate slot
layershifter Apr 8, 2019
41581f3
export play and pause icons
layershifter Apr 8, 2019
c2186e2
add stardust icons
layershifter Apr 8, 2019
f71b155
use Icon component, simplify styles
layershifter Apr 8, 2019
1e43a79
Merge branch 'master' of https://github.com/stardust-ui/react into fe…
layershifter Apr 8, 2019
8873cd4
update case
layershifter Apr 8, 2019
a09438a
Merge branch 'master' of https://github.com/stardust-ui/react into fe…
layershifter Apr 9, 2019
82453f5
fix UTs
layershifter Apr 9, 2019
2052501
fix merge issue
layershifter Apr 9, 2019
41fcec4
fix broken UTs
layershifter Apr 9, 2019
15eaf66
fix assignment of muted prop
layershifter Apr 9, 2019
4448b54
fix import
layershifter Apr 9, 2019
dbc89f4
fix condition
layershifter Apr 9, 2019
ae56edc
address comments
layershifter Apr 10, 2019
48618ee
Merge branch 'master' of https://github.com/stardust-ui/react into fe…
layershifter Apr 10, 2019
fff05b3
add changelog entry
layershifter Apr 10, 2019
1e6809a
Merge branch 'master' of https://github.com/stardust-ui/react into fe…
layershifter Apr 10, 2019
dae16b9
update styles
layershifter Apr 10, 2019
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,9 @@ This project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.htm
### Fixes
- Fix overflowing focus outline for `Grid` items for Teams theme @Bugaa92 ([#1195](https://github.com/stardust-ui/react/pull/1195))

### Features
- Add `Embed` and `Video` components @stuartlong ([#1108](https://github.com/stardust-ui/react/pull/1108))

<!--------------------------------[ v0.27.0 ]------------------------------- -->
## [v0.27.0](https://github.com/stardust-ui/react/tree/v0.27.0) (2019-04-10)
[Compare changes](https://github.com/stardust-ui/react/compare/v0.26.0...v0.27.0)
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
import * as React from 'react'
import { Embed, Flex, Text } from '@stardust-ui/react'

const EmbedExample = () => (
<Flex column>
<Embed
placeholder="https://github.com/raw/bower-media-samples/big-buck-bunny-480p-5s/master/poster.jpg"
video="https://github.com/raw/bower-media-samples/big-buck-bunny-1080p-5s/master/video.mp4"
defaultActive={true}
variables={{ height: '400px', width: '711.11px' }}
/>
<Text>(c) copyright 2008, Blender Foundation / www.bigbuckbunny.org</Text>
</Flex>
)

export default EmbedExample
16 changes: 16 additions & 0 deletions docs/src/examples/components/Embed/Types/index.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
import * as React from 'react'

import ComponentExample from 'docs/src/components/ComponentDoc/ComponentExample'
import ExampleSection from 'docs/src/components/ComponentDoc/ExampleSection'

const Types = () => (
<ExampleSection title="Types">
<ComponentExample
title="Embed"
description="A basic embedded GIF."
examplePath="components/Embed/Types/EmbedExample"
/>
</ExampleSection>
)

export default Types
11 changes: 11 additions & 0 deletions docs/src/examples/components/Embed/index.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
import * as React from 'react'

import Types from './Types'

const EmbedExamples = () => (
<>
<Types />
</>
)

export default EmbedExamples
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
import * as React from 'react'
import { Video, Text, Flex } from '@stardust-ui/react'

const VideoExample = () => (
<Flex column>
<Video
src="https://github.com/raw/bower-media-samples/big-buck-bunny-480p-30s/master/video.mp4"
variables={{ width: '600px' }}
/>
<Text>(c) copyright 2008, Blender Foundation / www.bigbuckbunny.org</Text>
</Flex>
)

export default VideoExample
16 changes: 16 additions & 0 deletions docs/src/examples/components/Video/Types/index.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
import * as React from 'react'

import ComponentExample from 'docs/src/components/ComponentDoc/ComponentExample'
import ExampleSection from 'docs/src/components/ComponentDoc/ExampleSection'

const Types = () => (
<ExampleSection title="Types">
<ComponentExample
title="Default"
description="A default Video."
examplePath="components/Video/Types/VideoExample"
/>
</ExampleSection>
)

export default Types
11 changes: 11 additions & 0 deletions docs/src/examples/components/Video/index.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
import * as React from 'react'

import Types from './Types'

const VideoExamples = () => (
<>
<Types />
</>
)

export default VideoExamples
187 changes: 187 additions & 0 deletions packages/react/src/components/Embed/Embed.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,187 @@
import * as _ from 'lodash'
import * as PropTypes from 'prop-types'
import * as React from 'react'
import * as customPropTypes from '@stardust-ui/react-proptypes'

import {
createShorthandFactory,
UIComponentProps,
applyAccessibilityKeyHandlers,
commonPropTypes,
AutoControlledComponent,
} from '../../lib'
import { embedBehavior } from '../../lib/accessibility'
import { Accessibility, AccessibilityActionHandlers } from '../../lib/accessibility/types'
import Icon, { IconProps } from '../Icon/Icon'
import Image, { ImageProps } from '../Image/Image'
import Video, { VideoProps } from '../Video/Video'
import Box from '../Box/Box'
import { ComponentEventHandler, ReactProps, ShorthandValue } from '../../types'

export interface EmbedSlotClassNames {
control: string
}

export interface EmbedProps extends UIComponentProps {
/**
* Accessibility behavior if overridden by the user.
* @default embedBehavior
* */
accessibility?: Accessibility

/** Whether the embedded object should be active. */
active?: boolean

/** Whether the embedded object should start active. */
defaultActive?: boolean

/** Shorthand for an control. */
control?: ShorthandValue<IconProps>

/** Shorthand for an embedded iframe. */
iframe?: ShorthandValue

/**
* Event for request to change 'active' value.
* @param {SyntheticEvent} event - React's original SyntheticEvent.
* @param {object} data - All props and proposed value.
*/
onActiveChanged?: ComponentEventHandler<EmbedProps>

/**
* Called when is clicked.
*
* @param {SyntheticEvent} event - React's original SyntheticEvent.
* @param {object} data - All item props.
*/
onClick?: ComponentEventHandler<EmbedProps>

/** Image source URL for when video isn't playing. */
placeholder?: ShorthandValue<ImageProps>

/** Shorthand for an embedded video. */
video?: ShorthandValue<VideoProps>
}

export interface EmbedState {
active: boolean
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Let's add onActiveChanged prop for this component, and invoke it when we change the active prop, as it will be required when we want to use it in controlled mode (see for example the onOpenChanged in the Popup component)

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Implemented as onActiveChanged, however I want address an inconsistency there separately because Popup has onOpenChange (without d on end).

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I would expect the d in the end, but agree, let's address in a separate PR, to avoid unnecessary breaking changes.

}

/**
* A GIF is a muted segment of a video
* @accessibility
* If GIF should be visible to screen readers, textual representation needs to be provided in 'alt' or 'title' property.
*
* Other considerations:
* - when alt and title property are empty, then Narrator in scan mode navigates to the gif and narrates it as empty paragraph
*/
class Embed extends AutoControlledComponent<ReactProps<EmbedProps>, EmbedState> {
static create: Function

static className = 'ui-embed'

static displayName = 'Embed'

static propTypes = {
...commonPropTypes.createCommon({
children: false,
content: false,
}),
active: PropTypes.bool,
defaultActive: PropTypes.bool,
control: customPropTypes.itemShorthand,
iframe: customPropTypes.itemShorthand,
onActiveChanged: PropTypes.func,
onClick: PropTypes.func,
placeholder: PropTypes.string,
video: customPropTypes.itemShorthand,
}

static defaultProps = {
as: 'span',
accessibility: embedBehavior as Accessibility,
control: {},
}

static autoControlledProps = ['active']

static slotClassNames: EmbedSlotClassNames = {
control: `${Embed.className}__control`,
}

actionHandlers: AccessibilityActionHandlers = {
performClick: event => this.handleClick(event),
}

getInitialAutoControlledState(): EmbedState {
return { active: false }
}

handleClick = e => {
e.stopPropagation()
e.preventDefault()

this.trySetState({ active: !this.state.active })
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is where we would invoke the onActiveChanged method.


_.invoke(this.props, 'onActiveChanged', e, { ...this.props, active: !this.state.active })
_.invoke(this.props, 'onClick', e, { ...this.props, active: !this.state.active })
}

renderComponent({ ElementType, classes, accessibility, unhandledProps, styles, variables }) {
const { control, iframe, placeholder, video } = this.props
const { active } = this.state

return (
<ElementType
className={classes.root}
onClick={this.handleClick}
{...accessibility.attributes.root}
{...applyAccessibilityKeyHandlers(accessibility.keyHandlers.root, unhandledProps)}
{...unhandledProps}
>
{active ? (
<>
{Video.create(video, {
defaultProps: {
autoPlay: true,
controls: false,
loop: true,
muted: true,
styles: styles.video,
variables: {
width: variables.width,
height: variables.height,
},
},
})}
{Box.create(iframe, { defaultProps: { as: 'iframe' } })}
</>
) : (
Image.create(placeholder, {
defaultProps: {
styles: styles.image,
variables: {
width: variables.width,
height: variables.height,
},
},
})
)}

{Icon.create(control, {
defaultProps: {
className: Embed.slotClassNames.control,
circular: true,
name: active ? 'stardust-pause' : 'stardust-play',
size: 'largest',
styles: styles.control,
},
})}
</ElementType>
)
}
}

Embed.create = createShorthandFactory({ Component: Embed })

export default Embed
105 changes: 105 additions & 0 deletions packages/react/src/components/Video/Video.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,105 @@
import * as PropTypes from 'prop-types'
import * as React from 'react'

import { createShorthandFactory, UIComponent, UIComponentProps, commonPropTypes } from '../../lib'

import { ReactProps } from '../../types'
import { defaultBehavior } from '../../lib/accessibility'
import Ref from '../Ref/Ref'

export interface VideoProps extends UIComponentProps {
/** Whether the video should start playing when rendered. Autoplay videos must be muted or they will not play immediately in certain browers like Chrome. */
autoPlay?: boolean

/** Whether to display the native video controls. */
controls?: boolean

/** Whether the video should automatically restart after it ends. */
loop?: boolean

/** Whether the video should be allowed to play audio. */
muted?: boolean

/** Image source URL for when video isn't playing. */
poster?: string

/** Video source URL. */
src?: string
}

/**
* An video is a graphicical and audio representation of something.
*/
class Video extends UIComponent<ReactProps<VideoProps>> {
static create: Function

static className = 'ui-video'

static displayName = 'Video'

static propTypes = {
...commonPropTypes.createCommon({
children: false,
content: false,
}),
autoPlay: PropTypes.bool,
controls: PropTypes.bool,
loop: PropTypes.bool,
muted: PropTypes.bool,
poster: PropTypes.string,
src: PropTypes.string,
}

static defaultProps = {
as: 'video',
accessibility: defaultBehavior,
controls: true,
autoPlay: false,
muted: false,
}

videoRef = React.createRef<HTMLVideoElement>()

componentDidMount() {
this.setVideoAttributes()
}

componentDidUpdate() {
this.setVideoAttributes()
}

setVideoAttributes = () => {
// React doesn't guaranty that props will be set:
// https://github.com/facebook/react/issues/10389
if (this.videoRef.current) {
if (this.props.muted) {
this.videoRef.current.setAttribute('muted', 'true')
} else {
this.videoRef.current.removeAttribute('muted')
}
}
}

renderComponent({ accessibility, ElementType, classes, unhandledProps }) {
const { controls, autoPlay, loop, poster, src } = this.props

return (
<Ref innerRef={this.videoRef}>
<ElementType
autoPlay={autoPlay}
className={classes.root}
controls={controls}
loop={loop}
poster={poster}
src={src}
{...accessibility.attributes.root}
{...unhandledProps}
/>
</Ref>
)
}
}

Video.create = createShorthandFactory({ Component: Video, mappedProp: 'src' })

export default Video
Loading