Skip to content

Commit 7c781fa

Browse files
authored
feature(b-icon): add animated icon options (closes #4720) (#4934)
Co-authored-by: Jacob Müller
1 parent c15f86a commit 7c781fa

16 files changed

+1752
-11
lines changed

docs/nuxt.config.js

+1-1
Original file line numberDiff line numberDiff line change
@@ -276,7 +276,7 @@ module.exports = {
276276
'highlight.js/styles/atom-one-light.css',
277277
'codemirror/lib/codemirror.css',
278278
'bootstrap/dist/css/bootstrap.css',
279-
'../scripts/build.scss', // BootstrapVue SCSS
279+
'../scripts/index.scss', // BootstrapVue SCSS
280280
'@assets/css/docs.min.css',
281281
'@assets/scss/styles.scss'
282282
]

scripts/build.sh

+3-3
Original file line numberDiff line numberDiff line change
@@ -96,16 +96,16 @@ node-sass --output-style expanded \
9696
--source-map true \
9797
--source-map-contents true \
9898
--precision 6 \
99-
scripts/build.scss \
99+
scripts/index.scss \
100100
dist/bootstrap-vue.css
101101
postcss --config scripts/postcss.config.js \
102102
--replace dist/bootstrap-vue.css
103-
# Icons only CSS
103+
# BootstrapVue Icons only CSS
104104
node-sass --output-style expanded \
105105
--source-map true \
106106
--source-map-contents true \
107107
--precision 6 \
108-
src/icons.scss \
108+
scripts/icons.scss \
109109
dist/bootstrap-vue-icons.css
110110
postcss --config scripts/postcss.config.js \
111111
--replace dist/bootstrap-vue-icons.css

scripts/icons.scss

+11
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
/*!
2+
* BootstrapVue Icons Custom CSS (https://bootstrap-vue.js.org)
3+
*/
4+
5+
// Include Bootstrap functions, variables, and mixins
6+
@import "node_modules/bootstrap/scss/functions";
7+
@import "node_modules/bootstrap/scss/variables";
8+
@import "node_modules/bootstrap/scss/mixins";
9+
10+
// Import BootstrapVue Icons custom SCSS
11+
@import "../src/icons.scss";
File renamed without changes.

src/_variables.scss

+7
Original file line numberDiff line numberDiff line change
@@ -59,6 +59,13 @@ $b-custom-file-height-inner-sm: calc(
5959
#{$b-custom-file-line-height-sm * 1em} + #{$b-custom-file-padding-y-sm * 2}
6060
) !default;
6161

62+
// --- Icons ---
63+
64+
// Animations
65+
$b-icon-animation-spin-duration: 2s !default;
66+
$b-icon-animation-pulse-duration: 1s !default;
67+
$b-icon-animation-cylon-duration: 0.75s !default;
68+
6269
// --- Tables ---
6370

6471
// Table busy state

src/components/spinner/README.md

+4
Original file line numberDiff line numberDiff line change
@@ -222,4 +222,8 @@ label is provided. You can easily customize the role if required via prop `role`
222222
As well, when no label is provided, the spinner will automatically have the attribute
223223
`aria-hidden="true"` to hide the spinner from screen reader users.
224224

225+
## See also
226+
227+
An alternative to the `<b-spinner>` component are [animated icons](/docs/icons/#animated-icons).
228+
225229
<!-- Component reference added automatically from component package.json -->

src/icons.scss

+4-1
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,8 @@
11
// --- BootstrapVue Icons Custom SCSS ---
22

3+
// Include variables and utilities first
4+
@import "variables";
5+
@import "utilities";
6+
37
// Include custom SCSS for icons
4-
// Temporary until Bootstrap v5 (maybe)
58
@import "icons/index";

src/icons/README.md

+61-1
Original file line numberDiff line numberDiff line change
@@ -297,7 +297,7 @@ With the use of Bootstrap's border and background
297297
<!-- icons-styling.vue -->
298298
```
299299

300-
## Transforms
300+
## SVG transforms
301301

302302
BootstrapVue icons provide several props for applying basic SVG transforms to the `<svg>`. All
303303
transforms can be combined for added effect. Note that the transforms are applied to the `<svg>`
@@ -428,6 +428,64 @@ Shifting is applied after any rotation transforms. As with scaling, backgrounds
428428
affected. If you need to shift the border/background with the icon, use Bootstrap's margin
429429
[spacing utility classes](/docs/reference/utility-classes).
430430

431+
## Animated icons
432+
433+
<span class="badge badge-info small">v2.7.0+</span>
434+
435+
BootstrapVue includes two spinning animation options for icons: `spin` and `pulse`. Both animations
436+
spin the icon clockwise, but pulse uses a stepped spin. A third animation called `cylon` is also
437+
provided.
438+
439+
To use the spin animation, set the `animation` prop to one of the animation names `'spin'`,
440+
`'pulse'` or `'cylon'`.
441+
442+
```html
443+
<template>
444+
<div>
445+
<p>Spinning animation:</p>
446+
<b-icon icon="arrow-clockwise" animation="spin" font-scale="4"></b-icon>
447+
448+
<p class="mt-3">Pulsing animation:</p>
449+
<b-icon icon="arrow-clockwise" animation="pulse" font-scale="4"></b-icon>
450+
451+
<p class="mt-3">Cylon animation:</p>
452+
<b-icon icon="three-dots" animation="cylon" font-scale="4"></b-icon>
453+
</div>
454+
</template>
455+
456+
<!-- b-icon-spin-aminations.vue -->
457+
```
458+
459+
Note with the `cylon` animation, the left-right movement extends past the icon's bounding box by
460+
`25%`, so you may need to adjust padding or margins to compensate for your use case.
461+
462+
As the animations are CSS based, they are applied _after_ any SVG transforms have taken place:
463+
464+
```html
465+
<template>
466+
<div class="p-4">
467+
<b-icon icon="clock" animation="spin" font-scale="4" shift-v="8"></b-icon>
468+
</div>
469+
</template>
470+
471+
<!-- b-icon-spin-aminations-transforms.vue -->
472+
```
473+
474+
The BootstrapVue defined icon animation effects require BootstrapVue's custom CSS. The `animation`
475+
prop translates to the class name `b-icon-animation-{animationName}`.
476+
477+
Need a different style animation? Just create a custom class defining the animation, and apply that
478+
class to the icon component, or create a new animation class in the form of
479+
`b-icon-animation-{animationName}` and pass the custom animation name to the `animation` prop.
480+
481+
**Note:** The BootstrapVue defined animation effects of this component is dependent on the
482+
`prefers-reduced-motion` media query. See the
483+
[reduced motion section of our accessibility documentation](/docs/reference/accessibility) for
484+
additional details.
485+
486+
Side note: the `cylon` animation gets its name from the "eye" of the Cylons from the _original_
487+
[1978 Battlestar Galactica TV series](https://www.youtube.com/watch?v=5a5bEIf0UaU).
488+
431489
## Stacking icons
432490

433491
<span class="badge badge-info small">v2.3.0+</span>
@@ -492,6 +550,8 @@ Stacked icon notes:
492550
- The `font-scale` prop cannot be used on the inner icon components
493551
- The `width` and `height` attributes cannot be applied to the inner icon components
494552
- Stacked icons **cannot** be stacked inside another `<b-iconstack>`
553+
- Note the animation props on the child icons will have no effect, however you _can_ use the
554+
animation props on the `<b-iconstack>` component.
495555

496556
## Using in components
497557

src/icons/_icons.scss

+41
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,47 @@
99
// Perhaps this values should be SASS variables?
1010
vertical-align: -0.15em;
1111
}
12+
13+
&.b-icon-animation-spin {
14+
animation: $b-icon-animation-spin-duration infinite linear b-icon-animation-spin;
15+
@media (prefers-reduced-motion: reduce) {
16+
animation: none;
17+
}
18+
}
19+
20+
&.b-icon-animation-pulse {
21+
animation: $b-icon-animation-pulse-duration infinite steps(8) b-icon-animation-spin;
22+
@media (prefers-reduced-motion: reduce) {
23+
animation: none;
24+
}
25+
}
26+
27+
&.b-icon-animation-cylon {
28+
animation: $b-icon-animation-cylon-duration infinite ease-in-out alternate
29+
b-icon-animation-cylon;
30+
@media (prefers-reduced-motion: reduce) {
31+
animation: none;
32+
}
33+
}
34+
}
35+
36+
// Animation for spinning icons
37+
@keyframes b-icon-animation-spin {
38+
0% {
39+
transform: rotate(0deg);
40+
}
41+
100% {
42+
transform: rotate(359deg);
43+
}
44+
}
45+
46+
@keyframes b-icon-animation-cylon {
47+
0% {
48+
transform: translateX(-25%);
49+
}
50+
100% {
51+
transform: translateX(25%);
52+
}
1253
}
1354

1455
// Make icons slightly larger in buttons, nav-links, dropdowns, and input-group-text

src/icons/helpers/icon-base.js

+9-1
Original file line numberDiff line numberDiff line change
@@ -37,6 +37,10 @@ export const commonIconProps = {
3737
shiftV: {
3838
type: [Number, String],
3939
default: 0
40+
},
41+
animation: {
42+
type: String,
43+
default: null
4044
}
4145
}
4246

@@ -73,6 +77,7 @@ export const BVIconBase = /*#__PURE__*/ Vue.extend({
7377
const shiftV = toFloat(props.shiftV) || 0
7478
const flipH = props.flipH
7579
const flipV = props.flipV
80+
const animation = props.animation
7681
// Compute the transforms
7782
// Note that order is important as SVG transforms are applied in order from
7883
// left to right and we want flipping/scale to occur before rotation
@@ -116,7 +121,10 @@ export const BVIconBase = /*#__PURE__*/ Vue.extend({
116121
mergeData(
117122
{
118123
staticClass: 'b-icon bi',
119-
class: { [`text-${props.variant}`]: !!props.variant },
124+
class: {
125+
[`text-${props.variant}`]: !!props.variant,
126+
[`b-icon-animation-${animation}`]: !!animation
127+
},
120128
attrs: baseAttrs,
121129
style: isStacked ? {} : { fontSize: fontScale === 1 ? null : `${fontScale * 100}%` }
122130
},

src/icons/icons.d.ts

+1-1
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
// --- BEGIN AUTO-GENERATED FILE ---
22
//
33
// @IconsVersion: 1.0.0-alpha2
4-
// @Generated: 2020-01-22T07:06:51.693Z
4+
// @Generated: 2020-03-12T09:18:47.956Z
55
//
66
// This file is generated on each build. Do not edit this file!
77

src/icons/icons.js

+1-1
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
// --- BEGIN AUTO-GENERATED FILE ---
22
//
33
// @IconsVersion: 1.0.0-alpha2
4-
// @Generated: 2020-01-22T07:06:51.693Z
4+
// @Generated: 2020-03-12T09:18:47.956Z
55
//
66
// This file is generated on each build. Do not edit this file!
77

src/icons/icons.spec.js

+18
Original file line numberDiff line numberDiff line change
@@ -430,4 +430,22 @@ describe('icons', () => {
430430
)
431431
expect(wrapper.find('svg > g > g > path').exists()).toBe(true)
432432
})
433+
434+
it('b-icon animation prop works', async () => {
435+
const wrapper = mount(BIcon, {
436+
localVue: localVue,
437+
parentComponent: parentComponent,
438+
propsData: {
439+
icon: 'circle-fill',
440+
animation: 'spin'
441+
}
442+
})
443+
444+
expect(wrapper.exists()).toBe(true)
445+
expect(wrapper.is('svg')).toBe(true)
446+
expect(wrapper.classes()).toContain('b-icon')
447+
expect(wrapper.classes()).toContain('bi')
448+
expect(wrapper.classes()).toContain('bi-circle-fill')
449+
expect(wrapper.classes()).toContain('b-icon-animation-spin')
450+
})
433451
})

0 commit comments

Comments
 (0)