Skip to content

Commit

Permalink
[TASK] Streamline spinner web component size and animation
Browse files Browse the repository at this point in the history
This change performs the following optimizations:

* Detach spinner from content flow using a relative+absolute
  position normalization, allowing an equivalent positioning
  to the regular backend icons.
  This is to ensure that both, <typo3-backend-spinner> and
  <typo3-backend-icon> render equally when used in inline(-block)
  containers (for example .svg-toolbar__drag-node).
  <typo3-backend-spinner> used to cause vertical alignment offsets,
  when the spinner was positioned in inline text-flow and therefore
  caused the vertical flow to cause offsets.

* Add a variant="light|dark" attribute to select
  between the available TYPO3 spinner variants.
  By default the current color is now used.

* Adapt sizing to inherit size from current font-size when used
  without a specific size attribute. Also apply that to the
  backend icon component to stay interchangeable.

* Render spinner via SVG and only animate the spinning part
  instead of the entire shape. This is to avoid the bouncing-icon
  effect that CSS animations on the entire element cause
  (as often seen with font-awesome spinners for example).

Releases: master
Resolves: #94149
Change-Id: I00d2e4915a0644726f78abe485fd9e276b539259
Reviewed-on: https://review.typo3.org/c/Packages/TYPO3.CMS/+/69162
Tested-by: Oliver Bartsch <bo@cedev.de>
Tested-by: Christian Kuhn <lolli@schwarzbu.ch>
Tested-by: core-ci <typo3@b13.com>
Tested-by: Benjamin Franzke <bfr@qbus.de>
Reviewed-by: Oliver Bartsch <bo@cedev.de>
Reviewed-by: Christian Kuhn <lolli@schwarzbu.ch>
Reviewed-by: Benjamin Franzke <bfr@qbus.de>
  • Loading branch information
bnf committed May 19, 2021
1 parent bce4af7 commit 1cfbf51
Show file tree
Hide file tree
Showing 6 changed files with 99 additions and 60 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,6 @@
* The TYPO3 project - inspiring people to share!
*/

import module = require('module');
import {html, css, unsafeCSS, LitElement, TemplateResult, CSSResult} from 'lit';
import {customElement, property} from 'lit/decorators';
import {unsafeHTML} from 'lit/directives/unsafe-html';
Expand All @@ -37,7 +36,7 @@ const iconSize = (identifier: CSSResult, size: number) => css`
@customElement('typo3-backend-icon')
export class IconElement extends LitElement {
@property({type: String}) identifier: string;
@property({type: String, reflect: true}) size: Sizes = Sizes.default;
@property({type: String}) size: Sizes = Sizes.default;
@property({type: String}) state: States = States.default;
@property({type: String}) overlay: string = null;
@property({type: String}) markup: MarkupIdentifiers = MarkupIdentifiers.inline;
Expand All @@ -58,15 +57,11 @@ export class IconElement extends LitElement {
css`
:host {
display: flex;
font-size: 1em;
width: 1em;
height: 1em;
line-height: 0;
}
typo3-backend-spinner {
font-size: 1em;
}
.icon {
position: relative;
display: block;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -15,52 +15,79 @@ import {html, css, LitElement, TemplateResult} from 'lit';
import {customElement, property} from 'lit/decorators';
import {Sizes} from '../Enum/IconTypes';

enum Variant {
light = 'light',
dark = 'dark'
}

/**
* Module: TYPO3/CMS/Backend/Element/SpinnerElement
*
* @example
* <typo3-backend-spinner size="small"></typo3-backend-spinner>
* + attribute size can be one of small, medium, large
* <typo3-backend-spinner size="small" variant="dark"></typo3-backend-spinner>
* + attribute size can be one of small, default, large or mega
*/
@customElement('typo3-backend-spinner')
export class SpinnerElement extends LitElement {
@property({type: String}) size: Sizes = Sizes.default;
@property({type: String}) variant: Variant = Variant.dark;

static styles = css`
:host {
font-size: 32px;
display: flex;
width: 1em;
height: 1em;
display: flex;
justify-content: center;
align-items: center;
line-height: 0;
}
.icon {
position: relative;
display: block;
height: 1em;
width: 1em;
line-height: 1;
}
.spinner {
svg {
position: absolute;
top: 0;
left: 0;
right: 0;
bottom: 0;
display: block;
border-style: solid;
border-color: #212121 #bababa #bababa;
border-radius: 50%;
width: 0.625em;
height: 0.625em;
border-width: 0.0625em;
animation: spin 1s linear infinite;
height: 1em;
width: 1em;
transform: translate3d(0, 0, 0);
fill: currentColor;
}
:host([variant=dark]) svg {
fill: #212121;
}
:host([size=small]) .spinner {
:host([variant=light]) svg {
fill: #fff;
}
:host([size=small]) {
font-size: 16px;
}
:host([size=large]) .spinner {
:host([size=default]) {
font-size: 32px;
}
:host([size=large]) {
font-size: 48px;
}
:host([size=mega]) .spinner {
:host([size=mega]) {
font-size: 64px;
}
@keyframes spin {
0% { transform: rotate(0deg); }
100% { transform: rotate(360deg); }
}
`;

public render(): TemplateResult {
return html`<div class="spinner"></div>`
return html`
<div class="icon">
<svg viewBox="0 0 16 16">
<path d="M8 15c-3.86 0-7-3.141-7-7 0-3.86 3.14-7 7-7 3.859 0 7 3.14 7 7 0 3.859-3.141 7-7 7zM8 3C5.243 3 3 5.243 3 8s2.243 5 5 5 5-2.243 5-5 -2.243-5-5-5z" opacity=".3"/>
<path d="M14 9a1 1 0 0 1-1-1c0-2.757-2.243-5-5-5a1 1 0 0 1 0-2c3.859 0 7 3.14 7 7a1 1 0 0 1-1 1z" transform-origin="center center">
<animateTransform attributeName="transform" attributeType="XML" type="rotate" from="0" to="360" begin="0s" dur="1s" repeatCount="indefinite" />
</path>
</svg>
</div>
`;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -47,7 +47,7 @@ export class CodeMirrorElement extends LitElement {
return html`
<slot></slot>
<slot name="codemirror"></slot>
${this.loaded ? '' : html`<typo3-backend-spinner size="large"></typo3-backend-spinner>`}
${this.loaded ? '' : html`<typo3-backend-spinner size="large" variant="dark"></typo3-backend-spinner>`}
`;
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -10,25 +10,21 @@
*
* The TYPO3 project - inspiring people to share!
*/
var __decorate=this&&this.__decorate||function(e,t,i,n){var o,r=arguments.length,s=r<3?t:null===n?n=Object.getOwnPropertyDescriptor(t,i):n;if("object"==typeof Reflect&&"function"==typeof Reflect.decorate)s=Reflect.decorate(e,t,i,n);else for(var a=e.length-1;a>=0;a--)(o=e[a])&&(s=(r<3?o(s):r>3?o(t,i,s):o(t,i))||s);return r>3&&s&&Object.defineProperty(t,i,s),s};define(["require","exports","lit","lit/decorators","lit/directives/unsafe-html","lit/directives/until","../Enum/IconTypes","../Icons","TYPO3/CMS/Backend/Element/SpinnerElement"],(function(e,t,i,n,o,r,s,a){"use strict";Object.defineProperty(t,"__esModule",{value:!0}),t.IconElement=void 0;const l=(e,t)=>i.css`
var __decorate=this&&this.__decorate||function(e,t,i,o){var n,r=arguments.length,s=r<3?t:null===o?o=Object.getOwnPropertyDescriptor(t,i):o;if("object"==typeof Reflect&&"function"==typeof Reflect.decorate)s=Reflect.decorate(e,t,i,o);else for(var a=e.length-1;a>=0;a--)(n=e[a])&&(s=(r<3?n(s):r>3?n(t,i,s):n(t,i))||s);return r>3&&s&&Object.defineProperty(t,i,s),s};define(["require","exports","lit","lit/decorators","lit/directives/unsafe-html","lit/directives/until","../Enum/IconTypes","../Icons","TYPO3/CMS/Backend/Element/SpinnerElement"],(function(e,t,i,o,n,r,s,a){"use strict";Object.defineProperty(t,"__esModule",{value:!0}),t.IconElement=void 0;const l=(e,t)=>i.css`
:host([size=${e}]),
:host([raw]) .icon-size-${e} {
font-size: ${t}px;
}
`;let c=class extends i.LitElement{constructor(){super(...arguments),this.size=s.Sizes.default,this.state=s.States.default,this.overlay=null,this.markup=s.MarkupIdentifiers.inline,this.raw=null}render(){if(this.raw)return i.html`${o.unsafeHTML(this.raw)}`;if(!this.identifier)return i.html``;const e=a.getIcon(this.identifier,this.size,this.overlay,this.state,this.markup).then(e=>i.html`
${o.unsafeHTML(e)}
`;let c=class extends i.LitElement{constructor(){super(...arguments),this.size=s.Sizes.default,this.state=s.States.default,this.overlay=null,this.markup=s.MarkupIdentifiers.inline,this.raw=null}render(){if(this.raw)return i.html`${n.unsafeHTML(this.raw)}`;if(!this.identifier)return i.html``;const e=a.getIcon(this.identifier,this.size,this.overlay,this.state,this.markup).then(e=>i.html`
${n.unsafeHTML(e)}
`);return i.html`${r.until(e,i.html`<typo3-backend-spinner></typo3-backend-spinner>`)}`}};c.styles=[i.css`
:host {
display: flex;
font-size: 1em;
width: 1em;
height: 1em;
line-height: 0;
}
typo3-backend-spinner {
font-size: 1em;
}
.icon {
position: relative;
display: block;
Expand Down Expand Up @@ -98,4 +94,4 @@ var __decorate=this&&this.__decorate||function(e,t,i,n){var o,r=arguments.length
transform: rotate(360deg);
}
}
`,l(i.unsafeCSS(s.Sizes.small),16),l(i.unsafeCSS(s.Sizes.default),32),l(i.unsafeCSS(s.Sizes.large),48),l(i.unsafeCSS(s.Sizes.mega),64)],__decorate([n.property({type:String})],c.prototype,"identifier",void 0),__decorate([n.property({type:String,reflect:!0})],c.prototype,"size",void 0),__decorate([n.property({type:String})],c.prototype,"state",void 0),__decorate([n.property({type:String})],c.prototype,"overlay",void 0),__decorate([n.property({type:String})],c.prototype,"markup",void 0),__decorate([n.property({type:String})],c.prototype,"raw",void 0),c=__decorate([n.customElement("typo3-backend-icon")],c),t.IconElement=c}));
`,l(i.unsafeCSS(s.Sizes.small),16),l(i.unsafeCSS(s.Sizes.default),32),l(i.unsafeCSS(s.Sizes.large),48),l(i.unsafeCSS(s.Sizes.mega),64)],__decorate([o.property({type:String})],c.prototype,"identifier",void 0),__decorate([o.property({type:String})],c.prototype,"size",void 0),__decorate([o.property({type:String})],c.prototype,"state",void 0),__decorate([o.property({type:String})],c.prototype,"overlay",void 0),__decorate([o.property({type:String})],c.prototype,"markup",void 0),__decorate([o.property({type:String})],c.prototype,"raw",void 0),c=__decorate([o.customElement("typo3-backend-icon")],c),t.IconElement=c}));
Original file line number Diff line number Diff line change
Expand Up @@ -10,36 +10,57 @@
*
* The TYPO3 project - inspiring people to share!
*/
var __decorate=this&&this.__decorate||function(e,t,r,n){var i,s=arguments.length,o=s<3?t:null===n?n=Object.getOwnPropertyDescriptor(t,r):n;if("object"==typeof Reflect&&"function"==typeof Reflect.decorate)o=Reflect.decorate(e,t,r,n);else for(var a=e.length-1;a>=0;a--)(i=e[a])&&(o=(s<3?i(o):s>3?i(t,r,o):i(t,r))||o);return s>3&&o&&Object.defineProperty(t,r,o),o};define(["require","exports","lit","lit/decorators","../Enum/IconTypes"],(function(e,t,r,n,i){"use strict";Object.defineProperty(t,"__esModule",{value:!0}),t.SpinnerElement=void 0;let s=class extends r.LitElement{constructor(){super(...arguments),this.size=i.Sizes.default}render(){return r.html`<div class="spinner"></div>`}};s.styles=r.css`
var __decorate=this&&this.__decorate||function(e,t,i,r){var o,n=arguments.length,s=n<3?t:null===r?r=Object.getOwnPropertyDescriptor(t,i):r;if("object"==typeof Reflect&&"function"==typeof Reflect.decorate)s=Reflect.decorate(e,t,i,r);else for(var a=e.length-1;a>=0;a--)(o=e[a])&&(s=(n<3?o(s):n>3?o(t,i,s):o(t,i))||s);return n>3&&s&&Object.defineProperty(t,i,s),s};define(["require","exports","lit","lit/decorators","../Enum/IconTypes"],(function(e,t,i,r,o){"use strict";var n;Object.defineProperty(t,"__esModule",{value:!0}),t.SpinnerElement=void 0,function(e){e.light="light",e.dark="dark"}(n||(n={}));let s=class extends i.LitElement{constructor(){super(...arguments),this.size=o.Sizes.default,this.variant=n.dark}render(){return i.html`
<div class="icon">
<svg viewBox="0 0 16 16">
<path d="M8 15c-3.86 0-7-3.141-7-7 0-3.86 3.14-7 7-7 3.859 0 7 3.14 7 7 0 3.859-3.141 7-7 7zM8 3C5.243 3 3 5.243 3 8s2.243 5 5 5 5-2.243 5-5 -2.243-5-5-5z" opacity=".3"/>
<path d="M14 9a1 1 0 0 1-1-1c0-2.757-2.243-5-5-5a1 1 0 0 1 0-2c3.859 0 7 3.14 7 7a1 1 0 0 1-1 1z" transform-origin="center center">
<animateTransform attributeName="transform" attributeType="XML" type="rotate" from="0" to="360" begin="0s" dur="1s" repeatCount="indefinite" />
</path>
</svg>
</div>
`}};s.styles=i.css`
:host {
font-size: 32px;
display: flex;
width: 1em;
height: 1em;
display: flex;
justify-content: center;
align-items: center;
line-height: 0;
}
.icon {
position: relative;
display: block;
height: 1em;
width: 1em;
line-height: 1;
}
.spinner {
svg {
position: absolute;
top: 0;
left: 0;
right: 0;
bottom: 0;
display: block;
border-style: solid;
border-color: #212121 #bababa #bababa;
border-radius: 50%;
width: 0.625em;
height: 0.625em;
border-width: 0.0625em;
animation: spin 1s linear infinite;
}
:host([size=small]) .spinner {
height: 1em;
width: 1em;
transform: translate3d(0, 0, 0);
fill: currentColor;
}
:host([variant=dark]) svg {
fill: #212121;
}
:host([variant=light]) svg {
fill: #fff;
}
:host([size=small]) {
font-size: 16px;
}
:host([size=large]) .spinner {
:host([size=default]) {
font-size: 32px;
}
:host([size=large]) {
font-size: 48px;
}
:host([size=mega]) .spinner {
:host([size=mega]) {
font-size: 64px;
}
@keyframes spin {
0% { transform: rotate(0deg); }
100% { transform: rotate(360deg); }
}
`,__decorate([n.property({type:String})],s.prototype,"size",void 0),s=__decorate([n.customElement("typo3-backend-spinner")],s),t.SpinnerElement=s}));
`,__decorate([r.property({type:String})],s.prototype,"size",void 0),__decorate([r.property({type:String})],s.prototype,"variant",void 0),s=__decorate([r.customElement("typo3-backend-spinner")],s),t.SpinnerElement=s}));

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

0 comments on commit 1cfbf51

Please sign in to comment.