Add progress circular (#32)

* made spinner more fancy

* rebuild spinner to progress circular

* update readme

* cleaned code and style

* clean readme

* made spinner more fancy

* rebuild spinner to progress circular

* update readme

* cleaned code and style

* clean readme

* Register circular progress

* Fix broken import

* Fix stylelint problems in v-progress-circular

* Add some useful tests for circular progress

* Delete package-lock.json

* Update readme

* Ignore package-lock

Co-authored-by: Rijk van Zanten <rijkvanzanten@me.com>
This commit is contained in:
Nitwel
2020-02-17 20:28:16 +01:00
committed by GitHub
parent 62bc8663a0
commit 9fcf702b4f
16 changed files with 412 additions and 258 deletions

1
.gitignore vendored
View File

@@ -3,6 +3,7 @@ node_modules
/dist
storybook-static
coverage
package-lock.json
# local env files
.env.local

View File

@@ -9,9 +9,9 @@ import VIcon from './v-icon/';
import VInput from './v-input/';
import VOverlay from './v-overlay/';
import VProgressLinear from './v-progress/linear/';
import VProgressCircular from './v-progress/circular/';
import VSheet from './v-sheet/';
import VSlider from './v-slider/';
import VSpinner from './v-spinner/';
import VSwitch from './v-switch/';
import VTable from './v-table/';
@@ -24,8 +24,8 @@ Vue.component('v-icon', VIcon);
Vue.component('v-input', VInput);
Vue.component('v-overlay', VOverlay);
Vue.component('v-progress-linear', VProgressLinear);
Vue.component('v-progress-circular', VProgressCircular);
Vue.component('v-sheet', VSheet);
Vue.component('v-slider', VSlider);
Vue.component('v-spinner', VSpinner);
Vue.component('v-switch', VSwitch);
Vue.component('v-table', VTable);

View File

@@ -1,12 +1,11 @@
import { mount, createLocalVue } from '@vue/test-utils';
import VueCompositionAPI from '@vue/composition-api';
import VButton from './v-button.vue';
import VProgressCircular from '../v-progress/circular/';
const localVue = createLocalVue();
localVue.use(VueCompositionAPI);
localVue.component('v-spinner', VSpinner);
import VButton from './v-button.vue';
import VSpinner from '../v-spinner/';
localVue.component('v-progress-circular', VProgressCircular);
describe('Button', () => {
it('Renders the provided markup in the default slow', () => {

View File

@@ -9,7 +9,7 @@
<span class="content" :class="{ invisible: loading }"><slot /></span>
<div class="spinner">
<slot v-if="loading" name="loading">
<v-spinner :x-small="xSmall" :small="small" />
<v-progress-circular :x-small="xSmall" :small="small" indeterminate />
</slot>
</div>
</button>
@@ -191,6 +191,11 @@ export default createComponent({
top: 50%;
left: 50%;
transform: translate(-50%, -50%);
.v-progress-circular {
--v-progress-circular-color: var(--v-button-color);
--v-progress-circular-background-color: transparent;
}
}
}
</style>

View File

@@ -0,0 +1,4 @@
import VProgressCircular from './v-progress-circular.vue';
export { VProgressCircular };
export default VProgressCircular;

View File

@@ -0,0 +1,66 @@
# Progress (circular)
```html
<v-progress-circular />
```
## Colors
The color of the circular progressbar can be changed through the `--v-progress-circular-color` and `--v-progress-circular-background-color` css variable.
```html
<v-progress-circular/>
<style>
.v-progress-circular {
--v-progress-circular-color: var(--red-100);
--v-progress-circular-background-color: var(--red-600);
}
</style>
```
## Sizes
The circular progress component supports the following sizes through the use of props:
* x-small
* small
* (default)
* large
* x-large
```html
<v-progress-circular x-small />
<v-progress-circular small />
<v-progress-circular />
<v-progress-circular large />
<v-progress-circular x-large />
```
## Props
| Prop | Description | Default |
|----------------|-----------------------------------|-------------------------------------|
| `value` | The percentage value | `0` |
| `indeterminate`| Displays the loading animation | `false` |
| `x-small` | Render extra small | `false` |
| `small` | Render small | `false` |
| `large` | Render large | `false` |
| `x-large` | Render extra large | `false` |
## Slots
| Slot | Description | Data |
|-----------|--------------------------------------|------|
| _default_ | Rendered in the center of the circle | -- |
## Events
n/a
## CSS Variables
| Variable | Default |
|------------------------------------------|------------------------------------------|
| `--v-progress-circular-color` | `var(--loading-background-color-accent)` |
| `--v-progress-circular-background-color` | `var(--loading-background-color)` |
| `--v-progress-circular-transition` | `400ms` |
| `--v-progress-circular-speed` | `1s` |
| `--v-progress-circular-size` | `28px` |
| `--v-progress-circular-line-size` | `3px` |

View File

@@ -0,0 +1,123 @@
import {
withKnobs,
color,
optionsKnob as options,
number,
text,
boolean
} from '@storybook/addon-knobs';
import Vue from 'vue';
import VProgressCircular from './v-progress-circular.vue';
import markdown from './v-progress-circular.readme.md';
import withPadding from '../../../../.storybook/decorators/with-padding';
Vue.component('v-progress-circular', VProgressCircular);
export default {
title: 'Components / Progress (circular)',
component: VProgressCircular,
decorators: [withKnobs, withPadding],
parameters: {
notes: markdown
}
};
export const interactive = () => ({
props: {
value: {
default: number('Value', 60)
},
indeterminate: {
default: boolean('Indeterminate', false)
},
color: {
default: color('Color', '#263238')
},
backgroundColor: {
default: color('Background Color', '#cfd8dc')
},
size: {
default: options(
'Size',
{
'Extra Small': 'xSmall',
Small: 'small',
'(default)': 'default',
Large: 'large',
'Extra Large': 'xLarge'
},
'default',
{
display: 'select'
}
)
},
speed: {
default: text('Speed (css, eg 200ms)', '2s')
},
customSize: {
default: text('Size (in px)', '')
},
customLineSize: {
default: text('Line Size (in px)', '')
}
},
template: `
<v-progress-circular
:value="value"
:indeterminate="indeterminate"
:style="{
'--v-progress-circular-color': color,
'--v-progress-circular-background-color': backgroundColor,
'--v-progress-circular-speed': speed,
'--v-progress-circular-size': customSize,
'--v-progress-circular-line-size': customLineSize
}"
:x-small="size === 'xSmall'"
:small="size === 'small'"
:large="size === 'large'"
:x-large="size === 'xLarge'"
/>`
});
export const colors = () => `
<div style="display: flex; justify-content: space-around">
<v-progress-circular value="80" style="--v-progress-circular-color: var(--red)" />
<v-progress-circular value="15" style="--v-progress-circular-color: var(--blue)" />
<v-progress-circular indeterminate style="--v-progress-circular-color: var(--green)" />
<v-progress-circular value="45" style="--v-progress-circular-color: var(--amber); --v-progress-circular-background-color: var(--red)" />
<v-progress-circular value="65" style="--v-progress-circular-color: var(--purple)" />
</div>
`;
export const sizes = () => `
<div style="display: flex; justify-content: space-around">
<v-progress-circular value="45" x-small />
<v-progress-circular value="45" small />
<v-progress-circular value="45" />
<v-progress-circular value="45" large />
<v-progress-circular value="45" x-large />
</div>
`;
export const speed = () => `
<div style="display: flex; justify-content: space-around">
<v-progress-circular indeterminate style="--v-progress-circular-speed: 5s" />
<v-progress-circular indeterminate style="--v-progress-circular-speed: 2.5s" />
<v-progress-circular indeterminate />
<v-progress-circular indeterminate style="--v-progress-circular-speed: 1.5s" />
<v-progress-circular indeterminate style="--v-progress-circular-speed: 1.25s" />
</div>
`;
export const withSlot = () => `
<div style="display: flex; gap: 20px;">
<v-progress-circular value="60" large>
60%
</v-progress-circular>
<v-progress-circular value="60" large>
<v-icon name="add"/>
</v-progress-circular>
</div>
`;

View File

@@ -0,0 +1,63 @@
import { mount, createLocalVue, Wrapper } from '@vue/test-utils';
import VueCompositionAPI from '@vue/composition-api';
const localVue = createLocalVue();
localVue.use(VueCompositionAPI);
import VProgressCircular from './v-progress-circular.vue';
describe('Spinner', () => {
let component: Wrapper<Vue>;
beforeEach(() => (component = mount(VProgressCircular, { localVue })));
it('Adds the correct classes based on props', async () => {
component.setProps({
indeterminate: true
});
await component.vm.$nextTick();
expect(component.find('svg').classes()).toContain('indeterminate');
});
it('Calculates the correct stroke-dasharray', async () => {
component.setProps({
value: 0
});
await component.vm.$nextTick();
expect((component.vm as any).circleStyle).toEqual({
'stroke-dasharray': '0, 78.5'
});
component.setProps({
value: 25
});
await component.vm.$nextTick();
expect((component.vm as any).circleStyle).toEqual({
'stroke-dasharray': '19.625, 78.5'
});
component.setProps({
value: 50
});
await component.vm.$nextTick();
expect((component.vm as any).circleStyle).toEqual({
'stroke-dasharray': '39.25, 78.5'
});
component.setProps({
value: 75
});
await component.vm.$nextTick();
expect((component.vm as any).circleStyle).toEqual({
'stroke-dasharray': '58.875, 78.5'
});
component.setProps({
value: 100
});
await component.vm.$nextTick();
expect((component.vm as any).circleStyle).toEqual({
'stroke-dasharray': '78.5, 78.5'
});
});
});

View File

@@ -0,0 +1,142 @@
<template>
<div class="v-progress-circular" :class="sizeClass">
<svg class="circle" viewBox="0 0 30 30" :class="{ indeterminate }">
<path
class="circle-background"
d="M12.5,0A12.5,12.5,0,1,1,0,12.5,12.5,12.5,0,0,1,12.5,0Z"
transform="translate(2.5 2.5)"
/>
<path
class="circle-path"
:style="circleStyle"
d="M12.5,0A12.5,12.5,0,1,1,0,12.5,12.5,12.5,0,0,1,12.5,0Z"
transform="translate(2.5 2.5)"
/>
</svg>
<slot></slot>
</div>
</template>
<script lang="ts">
import { createComponent, computed } from '@vue/composition-api';
import parseCSSVar from '@/utils/parse-css-var';
import useSizeClass, { sizeProps } from '@/compositions/size-class';
export default createComponent({
props: {
indeterminate: {
type: Boolean,
default: false
},
value: {
type: Number,
default: 0
},
...sizeProps
},
setup(props) {
const sizeClass = useSizeClass(props);
const circleStyle = computed(() => ({
'stroke-dasharray': (props.value / 100) * 78.5 + ', 78.5'
}));
return { sizeClass, circleStyle };
}
});
</script>
<style lang="scss" scoped>
.v-progress-circular {
--v-progress-circular-color: var(--input-foreground-color);
--v-progress-circular-background-color: var(--input-border-color);
--v-progress-circular-transition: 400ms;
--v-progress-circular-speed: 2s;
--v-progress-circular-size: 28px;
--v-progress-circular-line-size: 3px;
position: relative;
display: flex;
align-items: center;
justify-content: center;
width: var(--v-progress-circular-size);
height: var(--v-progress-circular-size);
&.x-small {
--v-progress-circular-size: 12px;
--v-progress-circular-line-size: 4px;
}
&.small {
--v-progress-circular-size: 16px;
--v-progress-circular-line-size: 3px;
}
&.large {
--v-progress-circular-size: 48px;
--v-progress-circular-line-size: 2.5px;
}
&.x-large {
--v-progress-circular-size: 64px;
--v-progress-circular-line-size: 2px;
}
.circle {
position: absolute;
top: 0;
left: 0;
width: var(--v-progress-circular-size);
height: var(--v-progress-circular-size);
&-path {
transition: stroke-dasharray var(--v-progress-circular-transition) ease-in-out;
fill: transparent;
stroke: var(--v-progress-circular-color);
stroke-width: var(--v-progress-circular-line-size);
}
&.indeterminate {
animation: rotate var(--v-progress-circular-speed) infinite linear;
.circle-path {
animation: stroke var(--v-progress-circular-speed) infinite linear;
}
}
&-background {
fill: transparent;
stroke: var(--v-progress-circular-background-color);
stroke-width: var(--v-progress-circular-line-size);
}
}
}
@keyframes rotate {
0% {
transform: rotate(0deg);
}
50% {
transform: rotate(360deg);
}
100% {
transform: rotate(1080deg);
}
}
@keyframes stroke {
0% {
stroke-dasharray: 0, 78.5px;
}
50% {
stroke-dasharray: 78.5px, 78.5px;
}
100% {
stroke-dasharray: 0, 78.5px;
}
}
</style>

View File

@@ -64,6 +64,7 @@ export default createComponent({
--v-progress-linear-height: 4px;
--v-progress-linear-color: var(--input-foreground-color);
--v-progress-linear-background-color: var(--input-border-color);
--v-progress-linear-transition: 400ms;
position: relative;
display: flex;
@@ -80,6 +81,7 @@ export default createComponent({
left: 0;
height: 100%;
background-color: var(--v-progress-linear-color);
transition: width var(--v-progress-linear-transition) ease-in-out;
}
&.absolute {

View File

@@ -1,4 +0,0 @@
import VSpinner from './v-spinner.vue';
export { VSpinner };
export default VSpinner;

View File

@@ -1,67 +0,0 @@
# Spinner
```html
<v-spinner />
```
## Colors
The color of the spinner can be changed through the `--v-spinner-color` and `--v-spinner-background-color` css variable.
```html
<v-spinner style="--v-spinner-color: var(--red-400); --v-spinner-background-color: transparent;" />
```
The background color can be set in similar fashion:
```html
<v-spinner/>
<style>
.v-spinner {
--v-spinner-color: var(--red-100);
--v-spinner-background-color: var(--red-600);
}
</style>
```
## Sizes
The spinner component supports the following sizes through the use of props:
* x-small
* small
* (default)
* large
* x-large
```html
<v-spinner x-small />
<v-spinner small />
<v-spinner />
<v-spinner large />
<v-spinner x-large />
```
## Props
| Prop | Description | Default |
|-------------|-----------------------------------|-------------------------------------|
| `x-small` | Render extra small | `false` |
| `small` | Render small | `false` |
| `large` | Render large | `false` |
| `x-large` | Render extra large | `false` |
## Slots
n/a
## Events
n/a
## CSS Variables
| Variable | Default |
|--------------------------------|-------------------------------|
| `--v-spinner-color` | `var(--foreground-color)` |
| `--v-spinner-background-color` | `var(--background-color-alt)` |
| `--v-spinner-speed` | `1s` |
| `--v-spinner-size` | `28px` |
| `--v-spinner-line-size` | `3px` |

View File

@@ -1,97 +0,0 @@
import { withKnobs, color, optionsKnob as options, number, text } from '@storybook/addon-knobs';
import Vue from 'vue';
import VSpinner from './v-spinner.vue';
import markdown from './v-spinner.readme.md';
import withPadding from '../../../.storybook/decorators/with-padding';
Vue.component('v-spinner', VSpinner);
export default {
title: 'Components / Spinner',
component: VSpinner,
decorators: [withKnobs, withPadding],
parameters: {
notes: markdown
}
};
export const interactive = () => ({
props: {
color: {
default: color('Color', '#263238')
},
backgroundColor: {
default: color('Background Color', '#cfd8dc')
},
size: {
default: options(
'Size',
{
'Extra Small': 'xSmall',
Small: 'small',
'(default)': 'default',
Large: 'large',
'Extra Large': 'xLarge'
},
'default',
{
display: 'select'
}
)
},
speed: {
default: text('Speed (css, eg 200ms)', '1s')
},
customSize: {
default: text('Size (in px)', '28px')
},
customLineSize: {
default: text('Line Size (in px)', '3px')
}
},
template: `
<v-spinner
:style="{
'--v-spinner-color': color,
'--v-spinner-background-color': backgroundColor,
'--v-spinner-speed': speed,
'--v-spinner-size': customSize,
'--v-spinner-line-size': customLineSize
}"
:x-small="size === 'xSmall'"
:small="size === 'small'"
:large="size === 'large'"
:x-large="size === 'xLarge'"
/>`
});
export const colors = () => `
<div style="display: flex; justify-content: space-around">
<v-spinner style="--v-spinner-color: var(--red)" />
<v-spinner style="--v-spinner-color: transparent; --v-spinner-background-color: var(--blue)" />
<v-spinner style="--v-spinner-color: var(--green)" />
<v-spinner style="--v-spinner-color: var(--amber); --v-spinner-background-color: var(--red)" />
<v-spinner style="--v-spinner-color: var(--purple)" />
</div>
`;
export const sizes = () => `
<div style="display: flex; justify-content: space-around">
<v-spinner x-small />
<v-spinner small />
<v-spinner />
<v-spinner large />
<v-spinner x-large />
</div>
`;
export const speed = () => `
<div style="display: flex; justify-content: space-around">
<v-spinner style="--v-spinner-speed: 5s" />
<v-spinner style="--v-spinner-speed: 2.5s" />
<v-spinner />
<v-spinner style="--v-spinner-speed: 500ms" />
<v-spinner style="--v-spinner-speed: 250ms" />
</div>
`;

View File

@@ -1,17 +0,0 @@
import { mount, createLocalVue, Wrapper } from '@vue/test-utils';
import VueCompositionAPI from '@vue/composition-api';
const localVue = createLocalVue();
localVue.use(VueCompositionAPI);
import VSpinner from './v-spinner.vue';
describe('Spinner', () => {
let component: Wrapper<Vue>;
beforeEach(() => (component = mount(VSpinner, { localVue })));
it('Renders', () => {
expect(component.exists()).toBe(true);
});
});

View File

@@ -1,66 +0,0 @@
<template>
<div class="v-spinner" :class="sizeClass"></div>
</template>
<script lang="ts">
import { createComponent, computed } from '@vue/composition-api';
import parseCSSVar from '@/utils/parse-css-var';
import useSizeClass, { sizeProps } from '@/compositions/size-class';
export default createComponent({
props: sizeProps,
setup(props) {
const sizeClass = useSizeClass(props);
return { sizeClass };
}
});
</script>
<style lang="scss" scoped>
.v-spinner {
--v-spinner-color: var(--foreground-color);
--v-spinner-background-color: var(--background-color-alt);
--v-spinner-speed: 1s;
--v-spinner-size: 28px;
--v-spinner-line-size: 3px;
position: relative;
width: var(--v-spinner-size);
height: var(--v-spinner-size);
background-color: transparent;
border: var(--v-spinner-line-size) solid var(--v-spinner-background-color);
border-top: var(--v-spinner-line-size) solid var(--v-spinner-color);
border-radius: 100%;
animation: rotate var(--v-spinner-speed) infinite linear;
&.x-small {
--v-spinner-size: 12px;
--v-spinner-line-size: 2px;
}
&.small {
--v-spinner-size: 16px;
--v-spinner-line-size: 3px;
}
&.large {
--v-spinner-size: 48px;
--v-spinner-line-size: 4px;
}
&.x-large {
--v-spinner-size: 64px;
--v-spinner-line-size: 5px;
}
}
@keyframes rotate {
from {
transform: rotate(0deg);
}
to {
transform: rotate(360deg);
}
}
</style>