feat(progress-bar): add progress bar component (#16559)

resolves #16558
This commit is contained in:
Paul Stelzer
2018-12-10 23:03:52 +01:00
committed by Brandy Carney
parent a2c7b9558b
commit 9167fb4fe1
17 changed files with 832 additions and 19 deletions

View File

@ -0,0 +1,8 @@
@import "./progress-bar";
// iOS Progress bar
// --------------------------------------------------
:host {
height: 3px;
}

View File

@ -0,0 +1,8 @@
@import "./progress-bar";
// Material Design Progress bar
// --------------------------------------------------
:host {
height: 4px;
}

View File

@ -0,0 +1,229 @@
@import "../../themes/ionic.globals";
// Progress bar
// --------------------------------------------------
// Host has no background by default - this will be added to the progress-buffer-bar
:host {
/**
* @prop --background: Same as --buffer-background when using a determinate progress bar, otherwise it styles the background of the ion-progress-bar itself.
* @prop --progress-background: Color of the progress bar
* @prop --buffer-background: Color of the buffer bar
*/
--background: #{ion-color(primary, base, 0.2)};
--progress-background: #{ion-color(primary, base)};
--buffer-background: var(--background);
display: block;
position: relative;
width: 100%;
contain: strict;
overflow: hidden;
}
:host(.ion-color) {
--progress-background: #{current-color(base)};
--buffer-background: #{current-color(base, 0.2)};
}
// indeterminate has no progress-buffer-bar, so it will be added to the host
:host(.progress-bar-indeterminate) {
background: var(--buffer-background);
}
// Set the bars to full width and height
// QUESTION: Can this be simplified?
.progress,
.progress-indeterminate,
.indeterminate-bar-primary,
.indeterminate-bar-secondary,
.progress-buffer-bar,
.progress-buffer-bar:before,
.buffer-circles {
@include position(0, 0, 0, 0);
position: absolute;
width: 100%;
height: 100%;
}
// Determinate progress bar
// --------------------------------------------------
.progress,
.progress-buffer-bar {
@include transform-origin(start, top);
transition: transform 150ms linear;
}
// Progress and background bar
// --------------------------------------------------
.progress,
.progress-indeterminate {
background: var(--progress-background);
z-index: 2;
}
.progress-buffer-bar {
// It's currently here because --buffer-background has an alpha
// Otherwise the buffer circles would be seen through
background: #fff;
z-index: 1; // Make it behind the progress
&:before {
background: var(--buffer-background);
content: "";
}
}
// MD based animation on indeterminate type
// --------------------------------------------------
.indeterminate-bar-primary {
@include position(0, 0, 0, -145.166611%);
animation: primary-indeterminate-translate 2s infinite linear;
.progress-indeterminate {
animation: primary-indeterminate-scale 2s infinite linear;
}
}
.indeterminate-bar-secondary {
@include position(0, 0, 0, -54.888891%);
animation: secondary-indeterminate-translate 2s infinite linear;
.progress-indeterminate {
animation: secondary-indeterminate-scale 2s infinite linear;
}
}
// Buffer style
// --------------------------------------------------
.buffer-circles {
background: radial-gradient(ellipse at center, var(--buffer-background) 0%, var(--buffer-background) 30%, transparent 30%) repeat-x 5px center;
background-size: 10px 10px;
z-index: 0;
animation: buffering 450ms infinite linear;
}
// If reversed is set to true, the animation will be reversed
// and the bars starting at the top right
// --------------------------------------------------
:host(.progress-bar-reversed) {
.progress,
.progress-buffer-bar {
@include transform-origin(end, top);
}
.buffer-circles,
.indeterminate-bar-primary,
.indeterminate-bar-secondary,
.progress-indeterminate {
animation-direction: reverse;
}
}
// Animation Keyframes
// --------------------------------------------------
// Source: https://github.com/material-components/material-components-web/blob/master/packages/mdc-linear-progress/_keyframes.scss
@keyframes primary-indeterminate-translate {
0% {
transform: translateX(0);
}
20% {
animation-timing-function: cubic-bezier(.5, 0, .701732, .495819);
transform: translateX(0);
}
59.15% {
animation-timing-function: cubic-bezier(.302435, .381352, .55, .956352);
transform: translateX(83.67142%);
}
100% {
transform: translateX(200.611057%);
}
}
@keyframes primary-indeterminate-scale {
0% {
transform: scaleX(.08);
}
36.65% {
animation-timing-function: cubic-bezier(.334731, .12482, .785844, 1);
transform: scaleX(.08);
}
69.15% {
animation-timing-function: cubic-bezier(.06, .11, .6, 1);
transform: scaleX(.661479);
}
100% {
transform: scaleX(.08);
}
}
@keyframes secondary-indeterminate-translate {
0% {
animation-timing-function: cubic-bezier(.15, 0, .515058, .409685);
transform: translateX(0);
}
25% {
animation-timing-function: cubic-bezier(.31033, .284058, .8, .733712);
transform: translateX(37.651913%);
}
48.35% {
animation-timing-function: cubic-bezier(.4, .627035, .6, .902026);
transform: translateX(84.386165%);
}
100% {
transform: translateX(160.277782%);
}
}
@keyframes secondary-indeterminate-scale {
0% {
animation-timing-function: cubic-bezier(.205028, .057051, .57661, .453971);
transform: scaleX(.08);
}
19.15% {
animation-timing-function: cubic-bezier(.152313, .196432, .648374, 1.004315);
transform: scaleX(.457104);
}
44.15% {
animation-timing-function: cubic-bezier(.257759, -.003163, .211762, 1.38179);
transform: scaleX(.72796);
}
100% {
transform: scaleX(.08);
}
}
@keyframes buffering {
to {
transform: translateX(-10px);
}
}

View File

@ -0,0 +1,85 @@
import { Component, ComponentInterface, Prop } from '@stencil/core';
import { Color, Mode } from '../../interface';
import { clamp } from '../../utils/helpers';
import { createColorClasses } from '../../utils/theme';
@Component({
tag: 'ion-progress-bar',
styleUrls: {
ios: 'progress-bar.ios.scss',
md: 'progress-bar.md.scss'
},
shadow: true
})
export class ProgressBar implements ComponentInterface {
/**
* The mode determines which platform styles to use.
*/
@Prop() mode!: Mode;
/**
* The state of the progress bar, based on if the time the process takes is known or not.
* Default options are: `"determinate"` (no animation), `"indeterminate"` (animate from left to right).
*/
@Prop() type: 'determinate' | 'indeterminate' = 'determinate';
/**
* If true, reverse the progress bar direction.
*/
@Prop() reversed = false;
/**
* The value determines how much of the active bar should display when the
* `type` is `"determinate"`.
* The value should be between [0, 1].
*/
@Prop() value = 0;
/**
* If the buffer and value are smaller than 1, the buffer circles will show.
* The buffer should be between [0, 1].
*/
@Prop() buffer = 1;
/**
* The color to use from your application's color palette.
* Default options are: `"primary"`, `"secondary"`, `"tertiary"`, `"success"`, `"warning"`, `"danger"`, `"light"`, `"medium"`, and `"dark"`.
* For more information on colors, see [theming](/docs/theming/basics).
*/
@Prop() color?: Color;
hostData() {
const { color, type, reversed, value } = this;
return {
'role': 'progressbar',
'aria-valuenow': type === 'determinate' ? value : null,
'aria-valuemin': 0,
'aria-valuemax': 1,
class: {
...createColorClasses(color),
[`progress-bar-${type}`]: true,
'progress-bar-reversed': reversed,
}
};
}
render() {
if (this.type === 'indeterminate') {
return [
<div class="indeterminate-bar-primary"><span class="progress-indeterminate"></span></div>,
<div class="indeterminate-bar-secondary"><span class="progress-indeterminate"></span></div>
];
}
const value = clamp(0, this.value, 1);
const buffer = clamp(0, this.buffer, 1);
return [
<div class="progress" style={{ transform: `scaleX(${value})` }}></div>,
buffer !== 1 && <div class="buffer-circles"></div>,
<div class="progress-buffer-bar" style={{ transform: `scaleX(${buffer})` }}></div>,
];
}
}

View File

@ -0,0 +1,68 @@
# ion-progress-bar
ion-progress-bar is a horizontal progress bar to visualize the progression of an operation and activity. You can choose between two types: `determinate` and `indeterminate`.
## Progress Type
### Determinate
If the percentage of an operation is known, you should use the determinate type. This is the default type and the progress is represented by the `value` property.
A buffer shows circles as animation to indicate some activity. If the `buffer` property is smaller than 1 you can show the addditional buffering progress.
### Indeterminate
If an operation is in progress and it's not necessary to indicate how long it will take.
If you add `reversed="true"`, you receive a query which is used to indicate pre-loading.
<!-- Auto Generated Below -->
## Usage
### Javascript
```html
<!-- Default Progressbar -->
<ion-progress-bar></ion-progress-bar>
<!-- Default Progressbar with 50 percent -->
<ion-progress-bar value="0.5"></ion-progress-bar>
<!-- Colorize Progressbar -->
<ion-progress-bar color="primary" value="0.5"></ion-progress-bar>
<ion-progress-bar color="secondary" value="0.5"></ion-progress-bar>
<!-- Other types -->
<ion-progress-bar value="0.25" buffer="0.5"></ion-progress-bar>
<ion-progress-bar type="indeterminate"></ion-progress-bar>
<ion-progress-bar type="indeterminate" reversed="true"></ion-progress-bar>
```
## Properties
| Property | Attribute | Description | Type | Default |
| ---------- | ---------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ---------------------------------- | --------------- |
| `buffer` | `buffer` | If the buffer and value are smaller than 1, the buffer circles will show. The buffer should be between [0, 1]. | `number` | `1` |
| `color` | `color` | The color to use from your application's color palette. Default options are: `"primary"`, `"secondary"`, `"tertiary"`, `"success"`, `"warning"`, `"danger"`, `"light"`, `"medium"`, and `"dark"`. For more information on colors, see [theming](/docs/theming/basics). | `string \| undefined` | `undefined` |
| `mode` | `mode` | The mode determines which platform styles to use. | `"ios" \| "md"` | `undefined` |
| `reversed` | `reversed` | If true, reverse the progress bar direction. | `boolean` | `false` |
| `type` | `type` | The state of the progress bar, based on if the time the process takes is known or not. Default options are: `"determinate"` (no animation), `"indeterminate"` (animate from left to right). | `"determinate" \| "indeterminate"` | `'determinate'` |
| `value` | `value` | The value determines how much of the active bar should display when the `type` is `"determinate"`. The value should be between [0, 1]. | `number` | `0` |
## CSS Custom Properties
| Name | Description |
| ----------------------- | ------------------------------------------------------------------------------------------------------------------------------------- |
| `--background` | Same as --buffer-background when using a determinate progress bar, otherwise it styles the background of the ion-progress-bar itself. |
| `--buffer-background` | Color of the buffer bar |
| `--progress-background` | Color of the progress bar |
----------------------------------------------
*Built with [StencilJS](https://stenciljs.com/)*

View File

@ -0,0 +1,10 @@
import { newE2EPage } from '@stencil/core/testing';
test('progress-bar: basic', async () => {
const page = await newE2EPage({
url: '/src/components/progress-bar/test/basic?ionic:_testing=true'
});
const compare = await page.compareScreenshot();
expect(compare).toMatchScreenshot();
});

View File

@ -0,0 +1,144 @@
<!DOCTYPE html>
<html dir="ltr">
<head>
<meta charset="UTF-8">
<title>Progress Bar - Basic</title>
<meta name="viewport" content="width=device-width, initial-scale=1.0, minimum-scale=1.0, maximum-scale=1.0, user-scalable=no">
<link href="../../../../../css/ionic.bundle.css" rel="stylesheet">
<link href="../../../../../scripts/testing/styles.css" rel="stylesheet">
<script src="../../../../../dist/ionic.js"></script>
<style>
.custom-bar-background {
--buffer-background: red;
}
.no-bar-background {
--buffer-background: none;
}
ion-progress-bar {
margin: 10px 0;
}
</style>
</head>
<body>
<ion-app>
<ion-header>
<ion-toolbar>
<ion-title>Progress Bar - Basic</ion-title>
</ion-toolbar>
</ion-header>
<ion-content class="outer-content">
<ion-list>
<ion-list-header>
<ion-label>
Indeterminate
</ion-label>
</ion-list-header>
<ion-progress-bar type="indeterminate"></ion-progress-bar>
<ion-list-header>
<ion-label>
Indeterminate with secondary color
</ion-label>
</ion-list-header>
<ion-progress-bar color="secondary" type="indeterminate"></ion-progress-bar>
<ion-list-header>
<ion-label>
Indeterminate (reversed)
</ion-label>
</ion-list-header>
<ion-progress-bar type="indeterminate" reversed="true"></ion-progress-bar>
<ion-list-header>
<ion-label>
Determinate
</ion-label>
</ion-list-header>
<ion-progress-bar value="0.50"></ion-progress-bar>
<ion-list-header>
<ion-label>
Determinate (reversed)
</ion-label>
</ion-list-header>
<ion-progress-bar reversed="true" value="0.50"></ion-progress-bar>
<ion-list-header>
<ion-label>
Determinate (secondary color)
</ion-label>
</ion-list-header>
<ion-progress-bar color="secondary" value="0.50"></ion-progress-bar>
<ion-list-header>
<ion-label>
Determinate (with no bar background)
</ion-label>
</ion-list-header>
<ion-progress-bar class="no-bar-background" value="0.75"></ion-progress-bar>
<ion-list-header>
<ion-label>
Determinate (with a custom background)
</ion-label>
</ion-list-header>
<ion-progress-bar class="custom-bar-background" value="0.75"></ion-progress-bar>
<ion-list-header>
<ion-label>
Determinate (change progress with slider)
</ion-label>
</ion-list-header>
<ion-progress-bar id="progressBar"></ion-progress-bar>
<ion-item>
<ion-range pin="true" value="0" id="progressValue">
<ion-label slot="start">0</ion-label>
<ion-label slot="end">100</ion-label>
</ion-range>
</ion-item>
<ion-list-header>
<ion-label>
Buffer
</ion-label>
</ion-list-header>
<ion-progress-bar value="0.20" buffer="0.4"></ion-progress-bar>
<ion-list-header>
<ion-label>
Buffer (reversed)
</ion-label>
</ion-list-header>
<ion-progress-bar color="secondary" reversed="true" value="0.20" buffer="0.4"></ion-progress-bar>
<ion-list-header>
<ion-label>
Buffer (without value)
</ion-label>
</ion-list-header>
<ion-progress-bar color="tertiary" buffer="0"></ion-progress-bar>
</ion-list>
</ion-content>
</ion-app>
<script>
const progressValue = document.getElementById('progressValue');
const progressBar = document.getElementById('progressBar');
progressValue.addEventListener('ionChange', function (ev) {
progressBar.value = ev.detail.value / 100;
});
</script>
</body>
</html>

View File

@ -0,0 +1,128 @@
<!DOCTYPE html>
<html dir="ltr">
<head>
<meta charset="UTF-8">
<title>Progress Bar - Preview</title>
<meta name="viewport" content="width=device-width, initial-scale=1.0, minimum-scale=1.0, maximum-scale=1.0, user-scalable=no">
<link href="../../../../../css/ionic.bundle.css" rel="stylesheet">
<link href="../../../../../scripts/testing/styles.css" rel="stylesheet">
<script src="../../../../../dist/ionic.js"></script>
</head>
<body>
<ion-app>
<ion-header>
<ion-toolbar>
<ion-title>Progress Bar</ion-title>
</ion-toolbar>
</ion-header>
<ion-content class="outer-content">
<ion-list-header>
<ion-label>Progress bar output</ion-label>
</ion-list-header>
<ion-progress-bar value="0.25" buffer="1" id="bar"></ion-progress-bar>
<ion-list-header>
<ion-label>Value</ion-label>
</ion-list-header>
<ion-item>
<ion-range pin="true" value="25" id="progress-value">
<ion-label slot="start">0</ion-label>
<ion-label slot="end">100</ion-label>
</ion-range>
</ion-item>
<ion-item>
<ion-label>Reversed?</ion-label>
<ion-toggle slot="start" name="reversed" id="progress-reversed"></ion-toggle>
</ion-item>
<ion-radio-group id="progress-color" value="primary">
<ion-list-header>
<ion-label>Color</ion-label>
</ion-list-header>
<ion-item>
<ion-label>Primary (default)</ion-label>
<ion-radio value="primary" slot="start"></ion-radio>
</ion-item>
<ion-item>
<ion-label>Secondary </ion-label>
<ion-radio value="secondary" slot="start"></ion-radio>
</ion-item>
<ion-item>
<ion-label>Tertiary</ion-label>
<ion-radio value="tertiary" slot="start"></ion-radio>
</ion-item>
</ion-radio-group>
<ion-radio-group id="progress-type" value="determinate">
<ion-list-header>
<ion-label>Type</ion-label>
</ion-list-header>
<ion-item>
<ion-label>determinate (default)</ion-label>
<ion-radio value="determinate" slot="start"></ion-radio>
</ion-item>
<ion-item>
<ion-label>indeterminate </ion-label>
<ion-radio value="indeterminate" slot="start"></ion-radio>
</ion-item>
</ion-radio-group>
<ion-list-header>
<ion-label>Set Buffer value</ion-label>
</ion-list-header>
<ion-item>
<ion-range pin="true" value="100" id="progress-buffer">
<ion-label slot="start">0</ion-label>
<ion-label slot="end">100</ion-label>
</ion-range>
</ion-item>
</ion-content>
</ion-app>
<script>
const bar = document.getElementById('bar');
// Update value
const progressValue = document.getElementById('progress-value');
progressValue.addEventListener('ionChange', function (ev) {
bar.value = ev.detail.value / 100;
});
// Update buffer value
const progressBuffer = document.getElementById('progress-buffer');
progressBuffer.addEventListener('ionChange', function (ev) {
bar.buffer = ev.detail.value / 100;
});
// Set the color of the progress bar
const progressColor = document.getElementById('progress-color');
progressColor.addEventListener('ionSelect', (ev) => {
bar.color = ev.detail.value;
});
// Set type of progress bar
const progressType = document.getElementById('progress-type');
progressType.addEventListener('ionSelect', (ev) => {
bar.type = ev.detail.value;
});
// Reverse the progress bar
const progressReversed = document.getElementById('progress-reversed');
progressReversed.addEventListener('ionChange', function (ev) {
bar.reversed = ev.detail.checked;
});
</script>
</body>
</html>

View File

@ -0,0 +1,29 @@
<!DOCTYPE html>
<html dir="ltr">
<head>
<meta charset="UTF-8">
<title>Progress Bar - Standalone</title>
<meta name="viewport" content="width=device-width, initial-scale=1.0, minimum-scale=1.0, maximum-scale=1.0, user-scalable=no">
<link href="../../../../../css/core.css" rel="stylesheet">
<link href="../../../../../scripts/testing/styles.css" rel="stylesheet">
<script src="../../../../../dist/ionic.js"></script>
</head>
<body>
<h1>Default Progress Bar</h1>
<ion-progress-bar></ion-progress-bar>
<h1>Default Progress Bar with 50% width</h1>
<ion-progress-bar value="0.5"></ion-progress-bar>
<h1>Colorize Progress Bar</h1>
<ion-progress-bar color="primary" value="0.5"></ion-progress-bar>
<ion-progress-bar color="secondary" value="0.5"></ion-progress-bar>
<h1>Other types</h1>
<ion-progress-bar type="indeterminate"></ion-progress-bar>
</body>
</html>

View File

@ -0,0 +1,16 @@
```html
<!-- Default Progressbar -->
<ion-progress-bar></ion-progress-bar>
<!-- Default Progressbar with 50 percent -->
<ion-progress-bar value="0.5"></ion-progress-bar>
<!-- Colorize Progressbar -->
<ion-progress-bar color="primary" value="0.5"></ion-progress-bar>
<ion-progress-bar color="secondary" value="0.5"></ion-progress-bar>
<!-- Other types -->
<ion-progress-bar value="0.25" buffer="0.5"></ion-progress-bar>
<ion-progress-bar type="indeterminate"></ion-progress-bar>
<ion-progress-bar type="indeterminate" reversed="true"></ion-progress-bar>
```