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

feat(Loader): add support for SVG animations, update in Teams theme #1097

Merged
merged 15 commits into from
Mar 28, 2019
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
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
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@ This project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.htm
### Features
- Add `attached` prop on the `ChatMessage` component, which is automatically set by the `ChatItem` component @mnajdova ([#1100](https://github.com/stardust-ui/react/pull/1100))
- Align `Alert` component styles to latest design for Teams theme @Bugaa92 ([#1111](https://github.com/stardust-ui/react/pull/1111))
- Add support for SVG animations to `Loader`, update in Teams theme @layershifter ([#1097](https://github.com/stardust-ui/react/pull/1097))

<!--------------------------------[ v0.25.0 ]------------------------------- -->
## [v0.25.0](https://github.com/stardust-ui/react/tree/v0.25.0) (2019-03-26)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,14 +2,16 @@ import { Grid, Loader } from '@stardust-ui/react'
import * as React from 'react'

const LoaderExampleSize: React.FC = () => (
<Grid columns="3" variables={{ gridGap: '20px' }}>
<Loader size="smallest" />
<Loader size="smaller" />
<Loader size="small" />
<Grid columns="4" variables={{ gridGap: '20px' }}>
<Loader size="smallest" label="smallest" labelPosition="below" />
<Loader size="smaller" label="smaller" labelPosition="below" />
<Loader size="small" label="small" labelPosition="below" />

<Loader size="large" />
<Loader size="larger" />
<Loader size="largest" />
<Loader label="medium (default)" labelPosition="below" />

<Loader size="large" label="large" labelPosition="below" />
<Loader size="larger" label="larger" labelPosition="below" />
<Loader size="largest" label="largest" labelPosition="below" />
</Grid>
)

Expand Down
4 changes: 2 additions & 2 deletions docs/src/prototypes/dropdowns/AsyncDropdownSearch.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -57,7 +57,7 @@ class AsyncDropdownSearch extends React.Component<{}, SearchPageState> {
if (this.state.items.length > 10) return

this.setState({ loading: true })
this.searchTimer = setTimeout(() => {
this.searchTimer = window.setTimeout(() => {
this.setState(prevState => ({
loading: false,
items: [...prevState.items, ..._.times<Entry>(2, createEntry)],
Expand All @@ -76,7 +76,7 @@ class AsyncDropdownSearch extends React.Component<{}, SearchPageState> {
items={items}
loading={loading}
loadingMessage={{
content: <Loader label="Loading..." labelPosition="end" size="larger" />,
content: <Loader label="Loading..." labelPosition="end" />,
}}
multiple
onSearchQueryChange={this.handleSearchQueryChange}
Expand Down
36 changes: 32 additions & 4 deletions packages/react/src/components/Loader/Loader.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,12 @@ import Box from '../Box/Box'

export type LoaderPosition = 'above' | 'below' | 'start' | 'end'

export interface LoaderSlotClassNames {
indicator: string
label: string
svg: string
}

export interface LoaderProps extends UIComponentProps, ColorComponentProps {
/**
* Accessibility behavior if overridden by the user.
Expand All @@ -41,6 +47,9 @@ export interface LoaderProps extends UIComponentProps, ColorComponentProps {

/** A size of the loader. */
size?: SizeValue

/** A loader can contain a custom svg element. */
svg?: ShorthandValue
}

export interface LoaderState {
Expand All @@ -54,6 +63,11 @@ class Loader extends UIComponent<ReactProps<LoaderProps>, LoaderState> {
static create: Function
static displayName = 'Loader'
static className = 'ui-loader'
static slotClassNames: LoaderSlotClassNames = {
indicator: `${Loader.className}__indicator`,
label: `${Loader.className}__label`,
svg: `${Loader.className}__svg`,
}

static propTypes = {
...commonPropTypes.createCommon({
Expand All @@ -67,13 +81,15 @@ class Loader extends UIComponent<ReactProps<LoaderProps>, LoaderState> {
label: customPropTypes.itemShorthand,
labelPosition: PropTypes.oneOf(['above', 'below', 'start', 'end']),
size: customPropTypes.size,
svg: customPropTypes.itemShorthand,
}

static defaultProps = {
accessibility: loaderBehavior,
delay: 0,
indicator: '',
indicator: {},
labelPosition: 'below',
svg: '',
size: 'medium',
}

Expand Down Expand Up @@ -102,18 +118,30 @@ class Loader extends UIComponent<ReactProps<LoaderProps>, LoaderState> {
}

renderComponent({ ElementType, classes, accessibility, variables, styles, unhandledProps }) {
const { indicator, label } = this.props
const { indicator, label, svg } = this.props
const { visible } = this.state

const svgElement = Box.create(svg, {
Copy link
Contributor

Choose a reason for hiding this comment

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

I am wondering whether the better shorthand for the svg would be the Icon component.. Can you elaborate why we are using the Box here?

Copy link
Member Author

Choose a reason for hiding this comment

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

Can you please clarify why it can be Icon? For example, this loader is an extra big SVG:

image

And actually, I don't see any way how spinners below can be used as icons:

image

Copy link
Contributor

Choose a reason for hiding this comment

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

Make sense, I mention icon only because it is an svg

defaultProps: { className: Loader.slotClassNames.svg, styles: styles.svg },
})

return (
visible && (
<ElementType
className={classes.root}
{...accessibility.attributes.root}
{...unhandledProps}
>
{Box.create(indicator, { defaultProps: { styles: styles.indicator } })}
{Box.create(label, { defaultProps: { styles: styles.label } })}
{Box.create(indicator, {
defaultProps: {
children: svgElement,
className: Loader.slotClassNames.indicator,
styles: styles.indicator,
},
})}
{Box.create(label, {
defaultProps: { className: Loader.slotClassNames.label, styles: styles.label },
})}
</ElementType>
)
)
Expand Down
2 changes: 2 additions & 0 deletions packages/react/src/themes/teams/componentStyles.ts
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,8 @@ export { default as Label } from './components/Label/labelStyles'

export { default as Layout } from './components/Layout/layoutStyles'

export { default as Loader } from './components/Loader/loaderStyles'

export { default as ItemLayout } from './components/ItemLayout/itemLayoutStyles'

export { default as List } from './components/List/listStyles'
Expand Down
2 changes: 2 additions & 0 deletions packages/react/src/themes/teams/componentVariables.ts
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,8 @@ export { default as Label } from './components/Label/labelVariables'

export { default as Layout } from './components/Layout/layoutVariables'

export { default as Loader } from './components/Loader/loaderVariables'

export { default as ItemLayout } from './components/ItemLayout/itemLayoutVariables'

export { default as ListItem } from './components/List/listItemVariables'
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,85 @@
import { LoaderProps } from '../../../../components/Loader/Loader'
import { ComponentStyleFunctionParam, ICSSInJSStyle } from '../../../types'
import { LoaderVariables } from './loaderVariables'

export default {
indicator: ({
props: p,
variables: v,
}: ComponentStyleFunctionParam<LoaderProps, LoaderVariables>): ICSSInJSStyle => ({
// Reset existing styles from base theme
animationName: 'none',
Copy link
Contributor

Choose a reason for hiding this comment

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

I wish we didn't have to reset here all existing styles from the base theme, but I don't have any other proposal. The styles for the base theme seems reasonable for it's usage

Copy link
Member Author

Choose a reason for hiding this comment

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

Yep, it's absolutely not funny, but I don't have a better idea now...

animationDuration: 'unset',
animationIterationCount: 'unset',
animationTimingFunction: 'unset',
borderColor: 'transparent',
borderRadius: 0,
borderStyle: 'none',
borderWidth: 0,

height: v.containerHeights[p.size],
width: v.containerWidths[p.size],
overflow: 'hidden',
}),
svg: ({
props: p,
theme: t,
variables: v,
}: ComponentStyleFunctionParam<LoaderProps, LoaderVariables>) => {
const outerAnimation: ICSSInJSStyle = {
animationName: t.renderer.renderKeyframe(
() =>
({
to: {
opacity: 1,
},
} as any),
{},
),
animationDelay: '1.5s',
animationDirection: 'normal',
animationDuration: '.3s',
animationFillMode: 'both',
animationIterationCount: '1',
animationPlayState: 'running',
animationTimingFunction: 'ease-out',
display: 'block',
overflow: 'hidden',
position: 'relative',
}
const svgAnimation: ICSSInJSStyle = {
animationName: t.renderer.renderKeyframe(
() =>
({
to: {
transform: `translate3d(0, ${v.svgTranslatePosition[p.size]}, 0)`,
},
} as any),
{},
),
animationDelay: '0s',
animationDirection: 'normal',
animationDuration: '2s',
animationFillMode: 'both',
animationPlayState: 'running',
animationTimingFunction: 'steps(60, end)',
animationIterationCount: 'infinite',
}

return {
...outerAnimation,

':before': {
Copy link
Contributor

Choose a reason for hiding this comment

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

How does this works? why we have animations for the svg and the :before? Can we somehow move this to the indicator and the svg accordingly?

Copy link
Contributor

Choose a reason for hiding this comment

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

Is this because of the svg that is used?

Copy link
Member Author

Choose a reason for hiding this comment

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

Idea with svg slot works fine, but Teams loader requires specific markup, i.e need one more element. Here is match:
image

I'm opened for better idea...

...svgAnimation,

backgroundImage: v.svgContent,
content: '" "',
display: 'block',
overflow: 'hidden',

height: v.svgHeights[p.size],
width: v.svgWidths[p.size],
},
}
},
}
Loading