diff --git a/app/soapbox/components/loading_spinner.js b/app/soapbox/components/loading_spinner.js new file mode 100644 index 000000000..a632454db --- /dev/null +++ b/app/soapbox/components/loading_spinner.js @@ -0,0 +1,20 @@ +/** + * iOS style loading spinner. + * It's mostly CSS, adapted from: https://loading.io/css/ + */ +import React from 'react'; +import PropTypes from 'prop-types'; + +const LoadingSpinner = ({ size = 30 }) => ( +
+ {Array(12).fill().map(i => ( +
+ ))} +
+); + +LoadingSpinner.propTypes = { + size: PropTypes.number, +}; + +export default LoadingSpinner; diff --git a/app/soapbox/components/pull_to_refresh.js b/app/soapbox/components/pull_to_refresh.js index 9ff1e6c88..f138e14ae 100644 --- a/app/soapbox/components/pull_to_refresh.js +++ b/app/soapbox/components/pull_to_refresh.js @@ -1,6 +1,7 @@ import React from 'react'; import PropTypes from 'prop-types'; import PTRComponent from 'react-simple-pull-to-refresh'; +import LoadingSpinner from 'soapbox/components/loading_spinner'; /** * PullToRefresh: @@ -32,9 +33,9 @@ export default class PullToRefresh extends React.Component { onRefresh={this.handleRefresh} pullingContent={null} // `undefined` will fallback to the default, while `null` will render nothing - refreshingContent={onRefresh ? undefined : null} - pullDownThreshold={130} - maxPullDownDistance={130} + refreshingContent={onRefresh ? : null} + pullDownThreshold={67} + maxPullDownDistance={95} resistance={2} {...rest} > diff --git a/app/styles/loading.scss b/app/styles/loading.scss index c26ec70fe..b893128ae 100644 --- a/app/styles/loading.scss +++ b/app/styles/loading.scss @@ -229,3 +229,114 @@ .ptr__children { overflow: visible !important; } + +.ptr .lds-spinner { + width: 40px; + height: 40px; +} + +.ptr__pull-down { + transform: translateY(10px); +} + +/** + * iOS style loading spinner. + * Adapted from: https://loading.io/css/ + * With some help scaling it: https://signalvnoise.com/posts/2577-loading-spinner-animation-using-css-and-webkit + */ +.lds-spinner { + display: inline-block; + position: relative; + width: 80px; + height: 80px; + + div { + position: absolute; + transform-origin: 50% 50%; + animation: lds-spinner 1.2s linear infinite; + width: 100%; + height: 100%; + + &::after { + content: ' '; + display: block; + position: absolute; + top: 3.75%; + left: 46.25%; + width: 7.5%; + height: 22.5%; + border-radius: 20%; + background: var(--primary-text-color); + } + + &:nth-child(1) { + transform: rotate(0deg); + animation-delay: -1.1s; + } + + &:nth-child(2) { + transform: rotate(30deg); + animation-delay: -1s; + } + + &:nth-child(3) { + transform: rotate(60deg); + animation-delay: -0.9s; + } + + &:nth-child(4) { + transform: rotate(90deg); + animation-delay: -0.8s; + } + + &:nth-child(5) { + transform: rotate(120deg); + animation-delay: -0.7s; + } + + &:nth-child(6) { + transform: rotate(150deg); + animation-delay: -0.6s; + } + + &:nth-child(7) { + transform: rotate(180deg); + animation-delay: -0.5s; + } + + &:nth-child(8) { + transform: rotate(210deg); + animation-delay: -0.4s; + } + + &:nth-child(9) { + transform: rotate(240deg); + animation-delay: -0.3s; + } + + &:nth-child(10) { + transform: rotate(270deg); + animation-delay: -0.2s; + } + + &:nth-child(11) { + transform: rotate(300deg); + animation-delay: -0.1s; + } + + &:nth-child(12) { + transform: rotate(330deg); + animation-delay: 0s; + } + } +} + +@keyframes lds-spinner { + 0% { + opacity: 1; + } + + 100% { + opacity: 0; + } +}