diff --git a/CHANGELOG.md b/CHANGELOG.md index 11e157148e..5037fff422 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -17,6 +17,9 @@ This project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.htm ## [Unreleased] +### Features +- Add `Loader` component @layershifter ([#685](https://github.com/stardust-ui/react/pull/685)) + ## [v0.16.1](https://github.com/stardust-ui/react/tree/v0.16.1) (2019-01-10) [Compare changes](https://github.com/stardust-ui/react/compare/v0.16.0...v0.16.1) diff --git a/docs/src/examples/components/Loader/Types/LoaderExample.tsx b/docs/src/examples/components/Loader/Types/LoaderExample.tsx new file mode 100644 index 0000000000..ba7aaf121d --- /dev/null +++ b/docs/src/examples/components/Loader/Types/LoaderExample.tsx @@ -0,0 +1,6 @@ +import { Loader } from '@stardust-ui/react' +import * as React from 'react' + +const LoaderExample: React.FC = () => + +export default LoaderExample diff --git a/docs/src/examples/components/Loader/Types/LoaderExampleLabel.shorthand.tsx b/docs/src/examples/components/Loader/Types/LoaderExampleLabel.shorthand.tsx new file mode 100644 index 0000000000..43ca493472 --- /dev/null +++ b/docs/src/examples/components/Loader/Types/LoaderExampleLabel.shorthand.tsx @@ -0,0 +1,6 @@ +import { Loader } from '@stardust-ui/react' +import * as React from 'react' + +const LoaderExampleLabel: React.FC = () => + +export default LoaderExampleLabel diff --git a/docs/src/examples/components/Loader/Types/index.tsx b/docs/src/examples/components/Loader/Types/index.tsx new file mode 100644 index 0000000000..30769a5a64 --- /dev/null +++ b/docs/src/examples/components/Loader/Types/index.tsx @@ -0,0 +1,21 @@ +import * as React from 'react' + +import ComponentExample from 'docs/src/components/ComponentDoc/ComponentExample' +import ExampleSection from 'docs/src/components/ComponentDoc/ExampleSection' + +const LoaderTypesExamples = () => ( + + + + +) + +export default LoaderTypesExamples diff --git a/docs/src/examples/components/Loader/Variations/LoaderExampleInline.tsx b/docs/src/examples/components/Loader/Variations/LoaderExampleInline.tsx new file mode 100644 index 0000000000..c11b08c161 --- /dev/null +++ b/docs/src/examples/components/Loader/Variations/LoaderExampleInline.tsx @@ -0,0 +1,6 @@ +import { Loader } from '@stardust-ui/react' +import * as React from 'react' + +const LoaderExampleInline: React.FC = () => + +export default LoaderExampleInline diff --git a/docs/src/examples/components/Loader/Variations/LoaderExampleLabelPosition.shorthand.tsx b/docs/src/examples/components/Loader/Variations/LoaderExampleLabelPosition.shorthand.tsx new file mode 100644 index 0000000000..677335a880 --- /dev/null +++ b/docs/src/examples/components/Loader/Variations/LoaderExampleLabelPosition.shorthand.tsx @@ -0,0 +1,14 @@ +import { Grid, Loader } from '@stardust-ui/react' +import * as React from 'react' + +const LoaderExampleLabel: React.FC = () => ( + + + + + + + +) + +export default LoaderExampleLabel diff --git a/docs/src/examples/components/Loader/Variations/LoaderExampleSize.tsx b/docs/src/examples/components/Loader/Variations/LoaderExampleSize.tsx new file mode 100644 index 0000000000..fbd19090f9 --- /dev/null +++ b/docs/src/examples/components/Loader/Variations/LoaderExampleSize.tsx @@ -0,0 +1,16 @@ +import { Grid, Loader } from '@stardust-ui/react' +import * as React from 'react' + +const LoaderExampleSize: React.FC = () => ( + + + + + + + + + +) + +export default LoaderExampleSize diff --git a/docs/src/examples/components/Loader/Variations/index.tsx b/docs/src/examples/components/Loader/Variations/index.tsx new file mode 100644 index 0000000000..de97527f08 --- /dev/null +++ b/docs/src/examples/components/Loader/Variations/index.tsx @@ -0,0 +1,26 @@ +import * as React from 'react' + +import ComponentExample from 'docs/src/components/ComponentDoc/ComponentExample' +import ExampleSection from 'docs/src/components/ComponentDoc/ExampleSection' + +const LoaderTypesExamples = () => ( + + + + + +) + +export default LoaderTypesExamples diff --git a/docs/src/examples/components/Loader/index.tsx b/docs/src/examples/components/Loader/index.tsx new file mode 100644 index 0000000000..e43a41ece4 --- /dev/null +++ b/docs/src/examples/components/Loader/index.tsx @@ -0,0 +1,13 @@ +import * as React from 'react' + +import Types from './Types' +import Variations from './Variations' + +const LoaderExamples = () => ( + <> + + + +) + +export default LoaderExamples diff --git a/src/components/Loader/Loader.tsx b/src/components/Loader/Loader.tsx new file mode 100644 index 0000000000..f39504385f --- /dev/null +++ b/src/components/Loader/Loader.tsx @@ -0,0 +1,89 @@ +import * as PropTypes from 'prop-types' +import * as React from 'react' + +import { + UIComponent, + createShorthandFactory, + UIComponentProps, + commonPropTypes, + ColorComponentProps, + customPropTypes, +} from '../../lib' +import { loaderBehavior } from '../../lib/accessibility' +import { Accessibility } from '../../lib/accessibility/types' +import { ReactProps, ShorthandValue } from '../../../types/utils' +import Slot from '../Slot/Slot' + +export type LoaderPosition = 'above' | 'below' | 'start' | 'end' +export type LoaderSize = + | 'smallest' + | 'smaller' + | 'small' + | 'medium' + | 'large' + | 'larger' + | 'largest' + +export interface LoaderProps extends UIComponentProps, ColorComponentProps { + /** + * Accessibility behavior if overridden by the user. + * @default defaultBehavior + */ + accessibility?: Accessibility + + /** A loader can contain an indicator. */ + indicator?: ShorthandValue + + /** A loader can contain a label. */ + label?: ShorthandValue + + /** A label in the loader can have different positions. */ + labelPosition?: LoaderPosition + + /** A size of the loader. */ + size?: LoaderSize +} + +/** + * A Loader indicates a possible user action. + */ +class Loader extends UIComponent> { + static create: Function + static displayName = 'Loader' + static className = 'ui-loader' + + static propTypes = { + ...commonPropTypes.createCommon({ + children: false, + content: false, + color: true, + }), + accessibility: PropTypes.func, + indicator: customPropTypes.itemShorthand, + label: customPropTypes.itemShorthand, + labelPosition: PropTypes.oneOf(['above', 'below', 'start', 'end']), + size: PropTypes.oneOf(['smallest', 'smaller', 'small', 'medium', 'large', 'larger', 'largest']), + } + + static defaultProps = { + accessibility: loaderBehavior, + indicator: '', + labelPosition: 'below', + size: 'medium', + } + + renderComponent({ ElementType, classes, accessibility, variables, styles, unhandledProps }) { + const { indicator, label } = this.props + + return ( + + {Slot.create(indicator, { defaultProps: { styles: styles.indicator } })} + {Slot.create(label, { defaultProps: { styles: styles.label } })} + + ) + } +} + +Loader.create = createShorthandFactory(Loader, 'content') + +export default Loader diff --git a/src/index.ts b/src/index.ts index ab806b3349..1d2a538e92 100644 --- a/src/index.ts +++ b/src/index.ts @@ -68,6 +68,7 @@ export { } from './components/ItemLayout/ItemLayout' export { default as Label, LabelProps } from './components/Label/Label' +export { default as Loader, LoaderProps } from './components/Loader/Loader' export { default as Layout, LayoutPropsWithDefaults, LayoutProps } from './components/Layout/Layout' diff --git a/src/lib/accessibility/Behaviors/Loader/loaderBehavior.ts b/src/lib/accessibility/Behaviors/Loader/loaderBehavior.ts new file mode 100644 index 0000000000..c9c8fb1282 --- /dev/null +++ b/src/lib/accessibility/Behaviors/Loader/loaderBehavior.ts @@ -0,0 +1,19 @@ +import { Accessibility } from '../../types' + +/** + * @description + * Loader is usually an element that displays the progress status for a task that take a long time or consists of several steps. + * + * @specification + * Adds role 'progressbar' to 'root' component's part. + */ + +const loaderBehavior: Accessibility = () => ({ + attributes: { + root: { + role: 'progressbar', + }, + }, +}) + +export default loaderBehavior diff --git a/src/lib/accessibility/index.ts b/src/lib/accessibility/index.ts index d2cd5944bd..a7dc324f89 100644 --- a/src/lib/accessibility/index.ts +++ b/src/lib/accessibility/index.ts @@ -12,6 +12,7 @@ export { default as listBehavior } from './Behaviors/List/listBehavior' export { default as listItemBehavior } from './Behaviors/List/listItemBehavior' export { default as selectableListBehavior } from './Behaviors/List/selectableListBehavior' export { default as selectableListItemBehavior } from './Behaviors/List/selectableListItemBehavior' +export { default as loaderBehavior } from './Behaviors/Loader/loaderBehavior' export { default as inputBehavior } from './Behaviors/Input/inputBehavior' export { default as iconBehavior } from './Behaviors/Icon/iconBehavior' export { default as tabBehavior } from './Behaviors/Tab/tabBehavior' diff --git a/src/themes/base/componentStyles.ts b/src/themes/base/componentStyles.ts index a15804c380..1c5c9b219a 100644 --- a/src/themes/base/componentStyles.ts +++ b/src/themes/base/componentStyles.ts @@ -1 +1,2 @@ +export { default as Loader } from './components/Loader/loaderStyles' export { default as Text } from './components/Text/textStyles' diff --git a/src/themes/base/componentVariables.ts b/src/themes/base/componentVariables.ts index 9acfb21044..f3c7863fd4 100644 --- a/src/themes/base/componentVariables.ts +++ b/src/themes/base/componentVariables.ts @@ -1 +1,2 @@ +export { default as Loader } from './components/Loader/loaderVariables' export { default as Text } from './components/Text/textVariables' diff --git a/src/themes/base/components/Loader/loaderStyles.ts b/src/themes/base/components/Loader/loaderStyles.ts new file mode 100644 index 0000000000..2ad77754f0 --- /dev/null +++ b/src/themes/base/components/Loader/loaderStyles.ts @@ -0,0 +1,64 @@ +import { FlexDirectionProperty } from 'csstype' + +import { pxToRem } from '../../../../lib' +import { LoaderProps } from '../../../../components/Loader/Loader' +import { ComponentStyleFunctionParam, ICSSInJSStyle } from '../../../types' +import { ObjectOf } from '../../../../../types/utils' +import { LoaderVariables } from './loaderVariables' + +const rootFlexDirections: ObjectOf = { + above: 'column-reverse', + below: 'column', + start: 'row-reverse', + end: 'row', +} + +export default { + root: ({ + props: p, + }: ComponentStyleFunctionParam): ICSSInJSStyle => ({ + alignItems: 'center', + display: p.inline ? 'inline-flex' : 'flex', + justifyContent: 'center', + flexDirection: rootFlexDirections[p.labelPosition], + }), + indicator: ({ + props: p, + theme: t, + variables: v, + }: ComponentStyleFunctionParam): ICSSInJSStyle => { + const animationName = t.renderer.renderKeyframe( + () => + ({ + from: { + transform: 'rotate(0deg)', + }, + to: { + transform: 'rotate(360deg)', + }, + } as any), + {}, + ) + const borderColor = `${v.foregroundColor} ${v.backgroundColor} ${v.backgroundColor}` + + return { + animationName, + animationDuration: '1.3s', + animationIterationCount: 'infinite', + animationTimingFunction: 'cubic-bezier(0.53, 0.21, 0.29, 0.67)', + + borderColor, + borderRadius: '50%', + borderStyle: 'solid', + borderWidth: v.borderSizes[p.size], + + boxSizing: 'border-box', + + width: v.indicatorSizes[p.size], + height: v.indicatorSizes[p.size], + } + }, + label: { + margin: pxToRem(10), + }, +} diff --git a/src/themes/base/components/Loader/loaderVariables.ts b/src/themes/base/components/Loader/loaderVariables.ts new file mode 100644 index 0000000000..14cd2d0d81 --- /dev/null +++ b/src/themes/base/components/Loader/loaderVariables.ts @@ -0,0 +1,38 @@ +import { pxToRem } from '../../../../lib' +import { LoaderSize } from '../../../../components/Loader/Loader' + +export interface LoaderVariables { + foregroundColor: string + backgroundColor: string + + borderSizes: Record + indicatorSizes: Record +} + +export default (siteVariables): LoaderVariables => ({ + foregroundColor: siteVariables.colors.grey[400], + backgroundColor: siteVariables.colors.grey[100], + + borderSizes: { + smallest: pxToRem(1.5), + small: pxToRem(2), + smaller: pxToRem(2), + + medium: pxToRem(2), + + large: pxToRem(2.5), + larger: pxToRem(3), + largest: pxToRem(4), + }, + indicatorSizes: { + smallest: pxToRem(16), + small: pxToRem(20), + smaller: pxToRem(22), + + medium: pxToRem(24), + + large: pxToRem(26), + larger: pxToRem(30), + largest: pxToRem(34), + }, +}) diff --git a/test/specs/behaviors/behavior-test.tsx b/test/specs/behaviors/behavior-test.tsx index f532331884..76070fd1eb 100644 --- a/test/specs/behaviors/behavior-test.tsx +++ b/test/specs/behaviors/behavior-test.tsx @@ -12,6 +12,7 @@ import { iconBehavior, imageBehavior, inputBehavior, + loaderBehavior, menuBehavior, menuItemBehavior, submenuBehavior, @@ -45,6 +46,7 @@ testHelper.addBehavior('buttonBehavior', buttonBehavior) testHelper.addBehavior('iconBehavior', iconBehavior) testHelper.addBehavior('inputBehavior', inputBehavior) testHelper.addBehavior('imageBehavior', imageBehavior) +testHelper.addBehavior('loaderBehavior', loaderBehavior) testHelper.addBehavior('menuBehavior', menuBehavior) testHelper.addBehavior('menuItemBehavior', menuItemBehavior) testHelper.addBehavior('submenuBehavior', submenuBehavior) diff --git a/test/specs/components/Loader/Loader-test.tsx b/test/specs/components/Loader/Loader-test.tsx new file mode 100644 index 0000000000..5e2d97684b --- /dev/null +++ b/test/specs/components/Loader/Loader-test.tsx @@ -0,0 +1,6 @@ +import { isConformant } from 'test/specs/commonTests' +import Loader from 'src/components/Loader/Loader' + +describe('Loader', () => { + isConformant(Loader) +})