feat: add loading animation on load more matches button

This commit is contained in:
Valentin Kaelin 2019-10-06 15:08:24 +02:00
parent e09c7d59b5
commit a7561a82b8
4 changed files with 136 additions and 5 deletions

View file

@ -0,0 +1,117 @@
<template>
<button
@click="btnClicked"
:class="[btnClass, {'loading': loading}, {'pr-12': loading}]"
:disabled="loading"
class="relative"
type="button"
>
<slot>Send</slot>
<span class="spinner absolute opacity-0 left-auto">
<span
class="inline-block absolute right-0 w-4 h-4 opacity-100 border-3 border-white rounded-full"
></span>
<span
class="inline-block absolute right-0 w-4 h-4 opacity-100 border-3 border-white rounded-full"
></span>
<span
class="inline-block absolute right-0 w-4 h-4 opacity-100 border-3 border-white rounded-full"
></span>
<span
class="inline-block absolute right-0 w-4 h-4 opacity-100 border-3 border-white rounded-full"
></span>
</span>
</button>
</template>
<script>
export default {
props: {
btnClass: {
type: String,
required: false,
default: ''
},
loading: {
type: Boolean,
required: false,
default: false
}
},
methods: {
btnClicked() {
this.$emit('clicked')
}
}
}
</script>
<style scoped>
button {
transition: all 0.2s;
transition-timing-function: ease-in;
}
.spinner {
top: 50%;
right: 1.7rem;
margin: -0.5rem;
transition-property: padding, opacity;
transition-duration: 0.2s, 0.2s;
transition-timing-function: ease-in, ease;
transition-delay: 0s, 0.2s;
}
.spinner span {
animation: spinner 1.2s cubic-bezier(0.5, 0, 0.5, 1) infinite;
border-color: #fff transparent transparent transparent;
}
.spinner span:nth-child(1) {
animation-delay: 0.45s;
}
.spinner span:nth-child(2) {
animation-delay: 0.3s;
}
.spinner span:nth-child(3) {
animation-delay: 0.15s;
}
.loading .spinner {
opacity: 1;
}
button:not(:disabled) .spinner span {
box-shadow: 0 0 0 0.2rem #4fd1c5 inset;
border: 7.4px solid transparent;
transition: all 0.4s;
}
button:not(:disabled) .spinner span:nth-child(1) {
transform: rotate(0deg);
}
button:not(:disabled) .spinner span:nth-child(2) {
transform: rotate(90deg);
}
button:not(:disabled) .spinner span:nth-child(3) {
transform: rotate(180deg);
}
button:not(:disabled) .spinner span:nth-child(4) {
transform: rotate(270deg);
}
@keyframes spinner {
0% {
transform: rotate(0deg);
}
100% {
transform: rotate(360deg);
}
}
</style>

View file

@ -11,11 +11,17 @@ export const state = {
matches: [], matches: [],
soloQ: {} soloQ: {}
}, },
matchesLoading: false,
status: '', status: '',
} }
export const mutations = { export const mutations = {
MATCHES_LOADING(state) {
state.matchesLoading = true
},
MATCHES_FOUND(state, newMatches) { MATCHES_FOUND(state, newMatches) {
state.matchesLoading = false
state.infos.matches = [...state.infos.matches, ...newMatches] state.infos.matches = [...state.infos.matches, ...newMatches]
state.infos.matchIndex += newMatches.length state.infos.matchIndex += newMatches.length
@ -38,6 +44,8 @@ export const mutations = {
export const actions = { export const actions = {
async moreMatches({ commit }) { async moreMatches({ commit }) {
commit('MATCHES_LOADING')
const account = state.infos.account const account = state.infos.account
const gameIds = state.infos.matchList.slice(state.infos.matchIndex, state.infos.matchIndex + 10).map(({ gameId }) => gameId) const gameIds = state.infos.matchList.slice(state.infos.matchIndex, state.infos.matchIndex + 10).map(({ gameId }) => gameId)
@ -69,6 +77,7 @@ export const actions = {
} }
export const getters = { export const getters = {
matchesLoading: state => state.matchesLoading,
moreMatchesToFetch: state => state.infos.matchIndex < state.infos.matchList.length, moreMatchesToFetch: state => state.infos.matchIndex < state.infos.matchList.length,
summonerFound: state => state.status === 'found', summonerFound: state => state.status === 'found',
summonerNotFound: state => state.status === 'error', summonerNotFound: state => state.status === 'error',

View file

@ -87,11 +87,13 @@
</ul> </ul>
</div> </div>
<button <LoadingButton
v-if="moreMatchesToFetch" v-if="moreMatchesToFetch"
@click="moreMatches" @clicked="moreMatches"
class="mt-4 block mx-auto bg-blue-800 px-4 py-2 rounded-md font-semibold hover:bg-blue-1000 shadow-lg" :loading="matchesLoading"
>More matches</button> btn-class="mt-4 block mx-auto bg-blue-800 px-4 py-2 rounded-md font-semibold hover:bg-blue-1000 shadow-lg"
>More matches</LoadingButton>
</div> </div>
</template> </template>
@ -113,6 +115,7 @@
<script> <script>
import { mapState, mapActions, mapGetters } from 'vuex' import { mapState, mapActions, mapGetters } from 'vuex'
import LazyBackground from '@/components/LazyBackgroundImage.vue' import LazyBackground from '@/components/LazyBackgroundImage.vue'
import LoadingButton from '@/components/LoadingButton.vue'
import RecentActivity from '@/components/RecentActivity.vue' import RecentActivity from '@/components/RecentActivity.vue'
import Match from '@/components/Match.vue' import Match from '@/components/Match.vue'
import SearchForm from '@/components/SearchForm.vue' import SearchForm from '@/components/SearchForm.vue'
@ -120,6 +123,7 @@ import SearchForm from '@/components/SearchForm.vue'
export default { export default {
components: { components: {
LazyBackground, LazyBackground,
LoadingButton,
Match, Match,
RecentActivity, RecentActivity,
SearchForm SearchForm
@ -138,7 +142,7 @@ export default {
...mapState({ ...mapState({
summonerInfos: state => state.summoner.infos summonerInfos: state => state.summoner.infos
}), }),
...mapGetters('summoner', ['moreMatchesToFetch', 'summonerFound', 'summonerNotFound', 'summonerLoading']) ...mapGetters('summoner', ['matchesLoading', 'moreMatchesToFetch', 'summonerFound', 'summonerNotFound', 'summonerLoading'])
}, },
watch: { watch: {

View file

@ -183,6 +183,7 @@ module.exports = {
default: '1px', default: '1px',
'0': '0', '0': '0',
'2': '2px', '2': '2px',
'3': '3px',
'4': '4px', '4': '4px',
'8': '8px', '8': '8px',
}, },