From 814ec765b901f96343a308f228da8311cb9f51a3 Mon Sep 17 00:00:00 2001 From: Liam DeBeasi Date: Fri, 3 Jan 2020 14:56:26 -0500 Subject: [PATCH] feat(refresher): add MD native refresher (#20096) resolves #17316 --- .../refresher-content/refresher-content.tsx | 17 +- core/src/components/refresher/readme.md | 12 +- .../components/refresher/refresher.md.scss | 71 +++++++ .../refresher/refresher.md.vars.scss | 16 +- core/src/components/refresher/refresher.scss | 13 ++ core/src/components/refresher/refresher.tsx | 188 ++++++++++++++---- .../components/refresher/refresher.utils.ts | 158 +++++++++++++-- .../components/refresher/test/spec/index.html | 34 +++- core/src/components/refresher/usage/react.md | 1 - .../src/components/spinner/spinner-configs.ts | 6 +- core/src/components/spinner/spinner.scss | 2 +- 11 files changed, 445 insertions(+), 73 deletions(-) diff --git a/core/src/components/refresher-content/refresher-content.tsx b/core/src/components/refresher-content/refresher-content.tsx index 078908f141..6e0a15b205 100644 --- a/core/src/components/refresher-content/refresher-content.tsx +++ b/core/src/components/refresher-content/refresher-content.tsx @@ -51,14 +51,14 @@ export class RefresherContent implements ComponentInterface { const mode = getIonMode(this); this.pullingIcon = config.get( 'refreshingIcon', - mode === 'ios' && isPlatform('mobile') ? config.get('spinner', 'lines') : 'arrow-down' + mode === 'ios' && isPlatform('mobile') ? config.get('spinner', 'lines') : 'circular' ); } if (this.refreshingSpinner === undefined) { const mode = getIonMode(this); this.refreshingSpinner = config.get( 'refreshingSpinner', - config.get('spinner', mode === 'ios' ? 'lines' : 'crescent') + config.get('spinner', mode === 'ios' ? 'lines' : 'circular') ); } } @@ -66,12 +66,21 @@ export class RefresherContent implements ComponentInterface { render() { const pullingIcon = this.pullingIcon; const hasSpinner = pullingIcon != null && SPINNERS[pullingIcon] as any !== undefined; + const mode = getIonMode(this); + return ( - +
{this.pullingIcon && hasSpinner &&
- +
+ + {mode === 'md' && this.pullingIcon === 'circular' && +
+ +
+ } +
} {this.pullingIcon && !hasSpinner && diff --git a/core/src/components/refresher/readme.md b/core/src/components/refresher/readme.md index 10f249f2cc..765cc26598 100644 --- a/core/src/components/refresher/readme.md +++ b/core/src/components/refresher/readme.md @@ -10,14 +10,19 @@ refresher. ### Native Refreshers -Both iOS and Android platforms provide refreshers that take advantage of properties exposed by their respective devices that give pull to refresh a fluid, native-like feel. One of the limitations of this is that the refreshers only work on their respective platform devices. For example, the iOS native `ion-refresher` works on an iPhone in iOS mode, but does not work on an Android device in iOS mode. In order for the refresher to work on an unsupported device, we provide a fallback refresher. This can also be set manually by overriding the `pullingIcon` property. +Both iOS and Android platforms provide refreshers that take advantage of properties exposed by their respective devices that give pull to refresh a fluid, native-like feel. -Because much of the native refreshers are based on scrolling, certain properties such as `pullMin` and `snapbackDuration` are not compatible. See [ion-refresher Properties](#properties) for more information. +Certain properties such as `pullMin` and `snapbackDuration` are not compatible because much of the native refreshers are scroll-based. See [Refresher Properties](#properties) for more information. #### iOS Usage -Using the iOS native `ion-refresher` requires setting the `pullingIcon` property on `ion-refresher-content` to the value of one of the available spinners. See the [ion-spinner Documentation](../spinner#properties) for accepted values. The `pullingIcon` defaults to the `lines` spinner on iOS. The spinner tick marks will be progressively shown as the user pulls down on the page. In order for the refresher to work on a device that isn't an iOS mobile device, the `pullingIcon` should be set to an icon. +Using the iOS native `ion-refresher` requires setting the `pullingIcon` property on `ion-refresher-content` to the value of one of the available spinners. See the [Spinner Documentation](../spinner#properties) for accepted values. The `pullingIcon` defaults to the `lines` spinner on iOS. The spinner tick marks will be progressively shown as the user pulls down on the page. +The iOS native `ion-refresher` relies on rubber band scrolling in order to work properly and is only compatible with iOS devices as a result. We provide a fallback refresher for apps running in iOS mode on devices that do not support rubber band scrolling. + +#### Android Usage + +Using the MD native `ion-refresher` requires setting the `pullingIcon` property on `ion-refresher-content` to the value of one of the available spinners. See the [ion-spinner Documentation](../spinner#properties) for accepted values. `pullingIcon` defaults to the `circular` spinner on MD. @@ -154,7 +159,6 @@ export const RefresherExample: React.FC = () => ( ); - ``` diff --git a/core/src/components/refresher/refresher.md.scss b/core/src/components/refresher/refresher.md.scss index 334734a17d..cff93bb394 100644 --- a/core/src/components/refresher/refresher.md.scss +++ b/core/src/components/refresher/refresher.md.scss @@ -25,3 +25,74 @@ .refresher-md .refresher-refreshing .spinner-dots circle { fill: $refresher-md-icon-color; } + +ion-refresher.refresher-native { + display: block; + + z-index: 1; + + ion-spinner { + @include margin(0, auto, 0, auto); + + width: 24px; + height: 24px; + + color: $refresher-md-native-spinner-color; + } + + .spinner-arrow-container { + display: inherit; + } + + .arrow-container { + display: block; + position: absolute; + + width: 24px; + height: 24px; + ion-icon { + @include margin(0, auto, 0, auto); + @include position(null, 0, -4px, 0); + + position: absolute; + + color: $refresher-md-native-spinner-color; + + font-size: 12px; + } + } + + &.refresher-pulling ion-refresher-content, + &.refresher-ready ion-refresher-content { + .refresher-pulling { + display: flex; + } + } + + &.refresher-refreshing ion-refresher-content, + &.refresher-completing ion-refresher-content, + &.refresher-cancelling ion-refresher-content { + .refresher-refreshing { + display: flex; + } + } + + .refresher-pulling-icon { + transform: translateY(calc(-100% - 10px)); + } + + .refresher-pulling-icon, + .refresher-refreshing-icon { + @include margin(0, auto, 0, auto); + @include border-radius(100%); + @include padding(8px, 8px, 8px, 8px); + + display: flex; + + border: $refresher-md-native-spinner-border; + + background: $refresher-md-native-spinner-background; + + box-shadow: $refresher-md-native-spinner-box-shadow; + } +} \ No newline at end of file diff --git a/core/src/components/refresher/refresher.md.vars.scss b/core/src/components/refresher/refresher.md.vars.scss index 87e74088f6..11d5ac73c7 100644 --- a/core/src/components/refresher/refresher.md.vars.scss +++ b/core/src/components/refresher/refresher.md.vars.scss @@ -1,7 +1,19 @@ @import "../../themes/ionic.globals.md"; /// @prop - Color of the refresher icon -$refresher-md-icon-color: $text-color !default; +$refresher-md-icon-color: $text-color !default; /// @prop - Text color of the refresher content -$refresher-md-text-color: $text-color !default; +$refresher-md-text-color: $text-color !default; + +/// @prop - Color of the native refresher spinner +$refresher-md-native-spinner-color: #{ion-color(primary, base)} !default; + +/// @prop - Border of the native refresher spinner +$refresher-md-native-spinner-border: 1px solid #ececec !default; + +/// @prop - Background of the native refresher spinner +$refresher-md-native-spinner-background: white !default; + +/// @prop - Box shadow of the native refresher spinner +$refresher-md-native-spinner-box-shadow: 0px 1px 6px rgba(0, 0, 0, 0.1) !default; diff --git a/core/src/components/refresher/refresher.scss b/core/src/components/refresher/refresher.scss index 855e246b21..b0b4c5febc 100644 --- a/core/src/components/refresher/refresher.scss +++ b/core/src/components/refresher/refresher.scss @@ -56,6 +56,10 @@ ion-refresher-content { text-align: center; } +ion-refresher-content .arrow-container { + display: none; +} + // Refresher Content States // -------------------------------------------------- @@ -100,3 +104,12 @@ ion-refresher-content { transform: scale(0); } } + +// Refresher Native +// -------------------------------------------------- + +.refresher-native { + .refresher-pulling-text, .refresher-refreshing-text { + display: none; + } +} \ No newline at end of file diff --git a/core/src/components/refresher/refresher.tsx b/core/src/components/refresher/refresher.tsx index f4bbae1b55..191edc53ac 100644 --- a/core/src/components/refresher/refresher.tsx +++ b/core/src/components/refresher/refresher.tsx @@ -1,11 +1,12 @@ import { Component, ComponentInterface, Element, Event, EventEmitter, Host, Method, Prop, State, Watch, h, readTask, writeTask } from '@stencil/core'; +import { getTimeGivenProgression } from '../../'; import { getIonMode } from '../../global/ionic-global'; -import { Gesture, GestureDetail, RefresherEventDetail } from '../../interface'; +import { Animation, Gesture, GestureDetail, RefresherEventDetail } from '../../interface'; import { clamp } from '../../utils/helpers'; import { hapticImpact } from '../../utils/native/haptic'; -import { handleScrollWhilePulling, handleScrollWhileRefreshing, setSpinnerOpacity, shouldUseNativeRefresher, translateElement } from './refresher.utils'; +import { createPullingAnimation, createSnapBackAnimation, getRefresherAnimationType, handleScrollWhilePulling, handleScrollWhileRefreshing, setSpinnerOpacity, shouldUseNativeRefresher, transitionEndAsync, translateElement } from './refresher.utils'; @Component({ tag: 'ion-refresher', @@ -28,6 +29,7 @@ export class Refresher implements ComponentInterface { private didRefresh = false; private lastVelocityY = 0; private elementToTransform?: HTMLElement; + private animations: Animation[] = []; @State() private nativeRefresher = false; @@ -142,26 +144,24 @@ export class Refresher implements ComponentInterface { private async resetNativeRefresher(el: HTMLElement | undefined, state: RefresherState) { this.state = state; - if (el !== undefined) { + if (getIonMode(this) === 'ios') { await translateElement(el, undefined); + } else { + await transitionEndAsync(this.el.querySelector('.refresher-refreshing-icon')); } this.didRefresh = false; this.needsCompletion = false; this.pointerDown = false; + this.animations.forEach(ani => ani.destroy()); + this.animations = []; + this.progress = 0; + this.state = RefresherState.Inactive; } - private async setupNativeRefresher(contentEl: HTMLIonContentElement | null) { - if (this.scrollListenerCallback || !contentEl) { - return; - } - - const pullingSpinner = this.el.querySelector('ion-refresher-content .refresher-pulling ion-spinner') as HTMLElement; - const refreshingSpinner = this.el.querySelector('ion-refresher-content .refresher-refreshing ion-spinner') as HTMLElement; - + private async setupiOSNativeRefresher(pullingSpinner: HTMLIonSpinnerElement, refreshingSpinner: HTMLIonSpinnerElement) { this.elementToTransform = this.scrollEl!.querySelector(`#scroll-content`) as HTMLElement | undefined; - this.nativeRefresher = true; const ticks = pullingSpinner.shadowRoot!.querySelectorAll('svg'); const MAX_PULL = this.scrollEl!.clientHeight * 0.16; const NUM_TICKS = ticks.length; @@ -198,7 +198,10 @@ export class Refresher implements ComponentInterface { this.ionStart.emit(); } - this.ionPull.emit(); + // emit "pulling" on every move + if (this.pointerDown) { + this.ionPull.emit(); + } } // delay showing the next tick marks until user has pulled 30px @@ -235,39 +238,144 @@ export class Refresher implements ComponentInterface { this.scrollEl!.addEventListener('scroll', this.scrollListenerCallback); this.gesture = (await import('../../utils/gesture')).createGesture({ - el: this.scrollEl!, - gestureName: 'refresher', - gesturePriority: 10, - direction: 'y', - threshold: 0, - onStart: () => { - this.pointerDown = true; + el: this.scrollEl!, + gestureName: 'refresher', + gesturePriority: 10, + direction: 'y', + threshold: 0, + onStart: () => { + this.pointerDown = true; - if (!this.didRefresh) { - translateElement(this.elementToTransform, '0px'); - } - }, - onMove: ev => { - this.lastVelocityY = ev.velocityY; - }, - onEnd: () => { - this.pointerDown = false; - this.didStart = false; + if (!this.didRefresh) { + translateElement(this.elementToTransform, '0px'); + } + }, + onMove: ev => { + this.lastVelocityY = ev.velocityY; + }, + onEnd: () => { + this.pointerDown = false; + this.didStart = false; - if (this.needsCompletion) { - this.resetNativeRefresher(this.elementToTransform, RefresherState.Completing); - this.needsCompletion = false; - } else if (this.didRefresh) { - readTask(() => { - translateElement(this.elementToTransform, `${this.el.clientHeight}px`); - }); - } - }, - }); + if (this.needsCompletion) { + this.resetNativeRefresher(this.elementToTransform, RefresherState.Completing); + this.needsCompletion = false; + } else if (this.didRefresh) { + readTask(() => translateElement(this.elementToTransform, `${this.el.clientHeight}px`)); + } + }, + }); this.disabledChanged(); } + private async setupMDNativeRefresher(contentEl: HTMLIonContentElement, pullingSpinner: HTMLIonSpinnerElement, refreshingSpinner: HTMLIonSpinnerElement) { + const circle = pullingSpinner.shadowRoot!.querySelector('circle'); + const pullingRefresherIcon = this.el.querySelector('ion-refresher-content .refresher-pulling-icon') as HTMLElement; + const refreshingCircle = refreshingSpinner.shadowRoot!.querySelector('circle'); + + if (circle !== null && refreshingCircle !== null) { + writeTask(() => { + circle.style.setProperty('animation', 'none'); + + // This lines up the animation on the refreshing spinner with the pulling spinner + refreshingSpinner.style.setProperty('animation-delay', '-655ms'); + refreshingCircle.style.setProperty('animation-delay', '-655ms'); + }); + } + + this.gesture = (await import('../../utils/gesture')).createGesture({ + el: this.scrollEl!, + gestureName: 'refresher', + gesturePriority: 10, + direction: 'y', + threshold: 0, + canStart: () => this.state !== RefresherState.Refreshing && this.state !== RefresherState.Completing && this.scrollEl!.scrollTop === 0, + onStart: (ev: GestureDetail) => { + ev.data = { animation: undefined, didStart: false, cancelled: false }; + }, + onMove: (ev: GestureDetail) => { + if ((ev.velocityY < 0 && this.progress === 0 && !ev.data.didStart) || ev.data.cancelled) { + ev.data.cancelled = true; + return; + } + + if (!ev.data.didStart) { + ev.data.didStart = true; + + this.state = RefresherState.Pulling; + + writeTask(() => { + const animationType = getRefresherAnimationType(contentEl); + const animation = createPullingAnimation(animationType, pullingRefresherIcon); + ev.data.animation = animation; + + this.scrollEl!.style.setProperty('--overflow', 'hidden'); + + animation.progressStart(false, 0); + this.ionStart.emit(); + this.animations.push(animation); + }); + + return; + } + + // Since we are using an easing curve, slow the gesture tracking down a bit + this.progress = clamp(0, (ev.deltaY / 180) * 0.5, 1); + ev.data.animation.progressStep(this.progress); + this.ionPull.emit(); + }, + onEnd: (ev: GestureDetail) => { + if (!ev.data.didStart) { return; } + + writeTask(() => this.scrollEl!.style.removeProperty('--overflow')); + if (this.progress <= 0.4) { + this.gesture!.enable(false); + + ev.data.animation + .progressEnd(0, this.progress, 500) + .onFinish(() => { + this.animations.forEach(ani => ani.destroy()); + this.animations = []; + this.gesture!.enable(true); + this.state = RefresherState.Inactive; + }); + return; + } + + const progress = getTimeGivenProgression([0, 0], [0, 0], [1, 1], [1, 1], this.progress)[0]; + const snapBackAnimation = createSnapBackAnimation(pullingRefresherIcon); + this.animations.push(snapBackAnimation); + writeTask(async () => { + pullingRefresherIcon.style.setProperty('--ion-pulling-refresher-translate', `${(progress * 100)}px`); + ev.data.animation.progressEnd(); + await snapBackAnimation.play(); + this.beginRefresh(); + ev.data.animation.destroy(); + }); + } + }); + + this.disabledChanged(); + } + + private async setupNativeRefresher(contentEl: HTMLIonContentElement | null) { + if (this.scrollListenerCallback || !contentEl || this.nativeRefresher) { + return; + } + + this.nativeRefresher = true; + + const pullingSpinner = this.el.querySelector('ion-refresher-content .refresher-pulling ion-spinner') as HTMLIonSpinnerElement; + const refreshingSpinner = this.el.querySelector('ion-refresher-content .refresher-refreshing ion-spinner') as HTMLIonSpinnerElement; + + if (getIonMode(this) === 'ios') { + this.setupiOSNativeRefresher(pullingSpinner, refreshingSpinner); + } else { + this.setupMDNativeRefresher(contentEl, pullingSpinner, refreshingSpinner); + } + } + componentDidUpdate() { this.checkNativeRefresher(); } diff --git a/core/src/components/refresher/refresher.utils.ts b/core/src/components/refresher/refresher.utils.ts index c0bbfae032..8dcda4d266 100644 --- a/core/src/components/refresher/refresher.utils.ts +++ b/core/src/components/refresher/refresher.utils.ts @@ -1,7 +1,123 @@ import { writeTask } from '@stencil/core'; +import { createAnimation } from '../../'; import { isPlatform } from '../../utils/platform'; +// MD Native Refresher +// ----------------------------- +type RefresherAnimationType = 'scale' | 'translate'; + +export const getRefresherAnimationType = (contentEl: HTMLIonContentElement): RefresherAnimationType => { + const previousSibling = contentEl.previousElementSibling; + const hasHeader = previousSibling !== null && previousSibling.tagName === 'ION-HEADER'; + + return hasHeader ? 'translate' : 'scale'; +}; + +export const createPullingAnimation = (type: RefresherAnimationType, pullingSpinner: HTMLElement) => { + return type === 'scale' ? createScaleAnimation(pullingSpinner) : createTranslateAnimation(pullingSpinner); +}; + +const createBaseAnimation = (pullingRefresherIcon: HTMLElement) => { + const spinner = pullingRefresherIcon.querySelector('ion-spinner') as HTMLElement; + const circle = spinner!.shadowRoot!.querySelector('circle') as any; + const spinnerArrowContainer = pullingRefresherIcon.querySelector('.spinner-arrow-container') as HTMLElement; + const arrowContainer = pullingRefresherIcon!.querySelector('.arrow-container'); + const arrow = (arrowContainer) ? arrowContainer!.querySelector('ion-icon') as HTMLElement : null; + + const baseAnimation = createAnimation() + .duration(1000) + .easing('ease-out'); + + const spinnerArrowContainerAnimation = createAnimation() + .addElement(spinnerArrowContainer) + .keyframes([ + { offset: 0, opacity: '0.3' }, + { offset: 0.45, opacity: '0.3' }, + { offset: 0.55, opacity: '1' }, + { offset: 1, opacity: '1' } + ]); + + const circleInnerAnimation = createAnimation() + .addElement(circle) + .keyframes([ + { offset: 0, 'stroke-dasharray': '1px, 200px' }, + { offset: 0.20, 'stroke-dasharray': '1px, 200px' }, + { offset: 0.55, 'stroke-dasharray': '100px, 200px' }, + { offset: 1, 'stroke-dasharray': '100px, 200px' } + ]); + + const circleOuterAnimation = createAnimation() + .addElement(spinner) + .keyframes([ + { offset: 0, transform: 'rotate(-90deg)' }, + { offset: 1, transform: 'rotate(210deg)' } + ]); + + /** + * Only add arrow animation if present + * this allows users to customize the spinners + * without errors being thrown + */ + if (arrowContainer && arrow) { + const arrowContainerAnimation = createAnimation() + .addElement(arrowContainer) + .keyframes([ + { offset: 0, transform: 'rotate(0deg)' }, + { offset: 0.30, transform: 'rotate(0deg)' }, + { offset: 0.55, transform: 'rotate(280deg)' }, + { offset: 1, transform: 'rotate(400deg)' } + ]); + + const arrowAnimation = createAnimation() + .addElement(arrow) + .keyframes([ + { offset: 0, transform: 'translateX(2px) scale(0)' }, + { offset: 0.30, transform: 'translateX(2px) scale(0)' }, + { offset: 0.55, transform: 'translateX(-1.5px) scale(1)' }, + { offset: 1, transform: 'translateX(-1.5px) scale(1)' } + ]); + + baseAnimation.addAnimation([arrowContainerAnimation, arrowAnimation]); + } + + return baseAnimation.addAnimation([spinnerArrowContainerAnimation, circleInnerAnimation, circleOuterAnimation]); +}; + +const createScaleAnimation = (pullingRefresherIcon: HTMLElement) => { + const height = pullingRefresherIcon.clientHeight; + const spinnerAnimation = createAnimation() + .addElement(pullingRefresherIcon) + .keyframes([ + { offset: 0, transform: `scale(0) translateY(-${height + 20}px)` }, + { offset: 1, transform: 'scale(1) translateY(100px)' } + ]); + + return createBaseAnimation(pullingRefresherIcon).addAnimation([spinnerAnimation]); +}; + +const createTranslateAnimation = (pullingRefresherIcon: HTMLElement) => { + const height = pullingRefresherIcon.clientHeight; + const spinnerAnimation = createAnimation() + .addElement(pullingRefresherIcon) + .keyframes([ + { offset: 0, transform: `translateY(-${height + 20}px)` }, + { offset: 1, transform: 'translateY(100px)' } + ]); + + return createBaseAnimation(pullingRefresherIcon).addAnimation([spinnerAnimation]); +}; + +export const createSnapBackAnimation = (pullingRefresherIcon: HTMLElement) => { + return createAnimation() + .duration(125) + .addElement(pullingRefresherIcon) + .fromTo('transform', 'translateY(var(--ion-pulling-refresher-translate, 100px))', 'translateY(0px)'); +}; + +// iOS Native Refresher +// ----------------------------- + export const setSpinnerOpacity = (spinner: HTMLElement, opacity: number) => { spinner.style.setProperty('opacity', opacity.toString()); }; @@ -29,6 +145,27 @@ export const handleScrollWhileRefreshing = ( }); }; +export const translateElement = (el?: HTMLElement, value?: string) => { + if (!el) { return Promise.resolve(); } + + const trans = transitionEndAsync(el); + + writeTask(() => { + el.style.setProperty('transition', '0.2s all ease-out'); + + if (value === undefined) { + el.style.removeProperty('transform'); + } else { + el.style.setProperty('transform', `translate3d(0px, ${value}, 0px)`); + } + }); + + return trans; +}; + +// Utils +// ----------------------------- + export const shouldUseNativeRefresher = (referenceEl: HTMLIonRefresherElement, mode: string) => { const pullingSpinner = referenceEl.querySelector('ion-refresher-content .refresher-pulling ion-spinner'); const refreshingSpinner = referenceEl.querySelector('ion-refresher-content .refresher-refreshing ion-spinner'); @@ -36,26 +173,17 @@ export const shouldUseNativeRefresher = (referenceEl: HTMLIonRefresherElement, m return ( pullingSpinner !== null && refreshingSpinner !== null && - mode === 'ios' && - isPlatform('mobile') + ( + (mode === 'ios' && isPlatform('mobile')) || + mode === 'md' + ) + ); }; -export const translateElement = (el?: HTMLElement, value?: string) => { +export const transitionEndAsync = (el: HTMLElement | null) => { return new Promise(resolve => { - if (!el) { return resolve(); } - transitionEnd(el, resolve); - - writeTask(() => { - el.style.setProperty('transition', '0.2s all ease-out'); - - if (value === undefined) { - el.style.removeProperty('transform'); - } else { - el.style.setProperty('transform', `translate3d(0px, ${value}, 0px)`); - } - }); }); }; diff --git a/core/src/components/refresher/test/spec/index.html b/core/src/components/refresher/test/spec/index.html index 3a96dd98de..8c188ddeab 100644 --- a/core/src/components/refresher/test/spec/index.html +++ b/core/src/components/refresher/test/spec/index.html @@ -44,11 +44,40 @@ + + + + + + - - + \ No newline at end of file diff --git a/core/src/components/refresher/usage/react.md b/core/src/components/refresher/usage/react.md index 4fe3fc1c60..a2cc671666 100644 --- a/core/src/components/refresher/usage/react.md +++ b/core/src/components/refresher/usage/react.md @@ -41,5 +41,4 @@ export const RefresherExample: React.FC = () => ( ); - ``` \ No newline at end of file diff --git a/core/src/components/spinner/spinner-configs.ts b/core/src/components/spinner/spinner-configs.ts index 07ac173fbf..632842f47a 100644 --- a/core/src/components/spinner/spinner-configs.ts +++ b/core/src/components/spinner/spinner-configs.ts @@ -44,10 +44,10 @@ const spinners = { fn: () => { return { r: 20, - cx: 44, - cy: 44, + cx: 48, + cy: 48, fill: 'none', - viewBox: '22 22 44 44', + viewBox: '24 24 48 48', transform: 'translate(0,0)', style: {} }; diff --git a/core/src/components/spinner/spinner.scss b/core/src/components/spinner/spinner.scss index ea1b75b4a3..eddfb393c8 100644 --- a/core/src/components/spinner/spinner.scss +++ b/core/src/components/spinner/spinner.scss @@ -111,7 +111,7 @@ svg { stroke: currentColor; stroke-dasharray: 80px, 200px; stroke-dashoffset: 0px; - stroke-width: 3.6; + stroke-width: 5.6; fill: none; }