Skip to content

Commit

Permalink
💄 add view transitions
Browse files Browse the repository at this point in the history
  • Loading branch information
krmax44 committed Jan 13, 2025
1 parent b679a25 commit a6b0c92
Show file tree
Hide file tree
Showing 5 changed files with 178 additions and 118 deletions.
35 changes: 34 additions & 1 deletion src/assets/style.css
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ body {

.btn {
@apply inline-flex items-center px-4 py-2 text-white font-medium;
@apply bg-blue-700 hover:bg-blue-800 focus:ring focus:ring-blue-900 outline-none transition;
@apply bg-blue-700 hover:bg-blue-800 focus:ring focus:ring-blue-900 outline-none motion-safe:transition;
}

.btn:disabled {
Expand Down Expand Up @@ -40,3 +40,36 @@ body {
font-style: normal;
font-display: swap;
}

@media (prefers-reduced-motion: no-preference) {
.slide-enter-active,
.slide-back-enter-active {
transition:
opacity 0.25s,
transform 0.25s theme(transitionTimingFunction.out);
}

.slide-leave-active,
.slide-back-leave-active {
transition:
opacity 0.25s,
transform 0.25s theme(transitionTimingFunction.in);
}

.slide-leave-to,
.slide-enter-from,
.slide-back-leave-to,
.slide-back-enter-from {
opacity: 0;
}

.slide-leave-to,
.slide-back-enter-from {
transform: translateX(-20%);
}

.slide-enter-from,
.slide-back-leave-to {
transform: translateX(20%);
}
}
67 changes: 34 additions & 33 deletions src/components/App.vue
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
<script lang="ts" setup>
import { computed, h, ref } from 'vue'
import { computed, ref } from 'vue'
import Questionnaire from './Questionnaire.vue'
import Results from './Results.vue'
import Weights from './Weights.vue'
Expand All @@ -18,6 +18,8 @@ const props = defineProps<{
questions: Question[]
}>()
const transitionName = ref('slide')
const reset = () => {
answers.value = {}
weightedTopics.value = []
Expand All @@ -35,55 +37,54 @@ const hasPreviousStage = computed(() => currentStage.value !== Stage.Intro)
const hasNextStage = computed(() => currentStage.value !== Stage.Results)
const nextStage = () => {
transitionName.value = 'slide'
if (hasNextStage.value) currentStage.value += 1
}
const previousStage = () => {
transitionName.value = 'slide-back'
if (hasPreviousStage.value) currentStage.value -= 1
}
</script>

<template>
<div v-if="currentStage === Stage.Intro" class="bg-white p-4">
<p class="text-xl mb-4">
Alle 7 Parteien, die in der letzten Legislaturperiode im Bundestag saßen,
haben über Anträge und Gesetzesentwürfe abgestimmt. Jetzt sind Sie an der
Reihe: Vergleichen Sie Ihre Standpunkte mit dem Abstimmungsverhalten der
Parteien.
</p>
<p>
Der Real-O-Mat ist keine Wahlempfehlung, sondern ein Informationsangebot
über Parteien und ihr Abstimmungsverhalten.
</p>

<button class="btn text-4xl mt-4" @click="nextStage">Los geht's!</button>
</div>

<Questionnaire
v-else-if="currentStage === Stage.Questionnaire"
:questions="questions"
@done="nextStage"
@previous="previousStage"
@reset="confirmReset"
/>

<Weights
v-else-if="currentStage === Stage.Weights"
:questions="questions"
@done="nextStage"
/>

<Results v-else :questions="questions" />
<Transition :name="transitionName" mode="out-in">
<div v-if="currentStage === Stage.Intro" class="bg-white p-4">
<p class="text-xl mb-4">
Alle 7 Parteien, die in der letzten Legislaturperiode im Bundestag
saßen, haben über Anträge und Gesetzesentwürfe abgestimmt. Jetzt sind
Sie an der Reihe: Vergleichen Sie Ihre Standpunkte mit dem
Abstimmungsverhalten der Parteien.
</p>
<p>
Der Real-O-Mat ist keine Wahlempfehlung, sondern ein Informationsangebot
über Parteien und ihr Abstimmungsverhalten.
</p>
<button class="btn text-4xl mt-4" @click="nextStage">Los geht's!</button>
</div>
<Questionnaire
v-else-if="currentStage === Stage.Questionnaire"
:questions="questions"
@done="nextStage"
@previous="previousStage"
@reset="confirmReset"
/>
<Weights
v-else-if="currentStage === Stage.Weights"
:questions="questions"
@done="nextStage"
/>
<Results v-else :questions="questions" />
</Transition>

<div
class="flex mt-4 text-blue-900 motion-safe:transition-all"
v-if="[Stage.Weights, Stage.Results].includes(currentStage)"
class="flex mt-4 text-blue-900"
>
<button @click="previousStage" class="btn-text">
<IconBack class="me-1" />
Zurück
</button>

<button @click="confirmReset" class="btn-text ms-auto">
<IconRestart class="me-1" />
Neustarten
Expand Down
186 changes: 105 additions & 81 deletions src/components/Questionnaire.vue
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
<script setup lang="ts">
import { computed, watch } from 'vue'
import { computed, ref, watch } from 'vue'
import type { Question, Answer } from '../content.config.ts'
import { answers, currentQuestionIndex } from '../store.ts'
import IconBack from '~icons/material-symbols/arrow-back'
Expand Down Expand Up @@ -27,7 +27,11 @@ const currentQuestion = computed(
)
const questionsCount = computed(() => props.questions.length)
const transitionName = ref('slide')
const nextQuestion = () => {
transitionName.value = 'slide'
if (currentQuestionIndex.value < props.questions.length - 1) {
currentQuestionIndex.value++
} else {
Expand All @@ -49,8 +53,13 @@ const skipQuestion = () => {
}
const previousQuestion = () => {
if (currentQuestionIndex.value > 0) currentQuestionIndex.value--
else emit('previous')
transitionName.value = 'slide-back'
if (currentQuestionIndex.value > 0) {
currentQuestionIndex.value--
} else {
emit('previous')
}
}
const partyAnswerExists = (answer: Answer): boolean =>
Expand All @@ -71,88 +80,103 @@ watch(currentQuestion, () => {
</script>

<template>
<div class="pb-4 bg-white">
<article v-if="currentQuestion">
<div
class="bg-blue-200"
role="progressbar"
aria-label="Fortschritt"
aria-valuemin="1"
:aria-valuemax="questionsCount"
:aria-valuenow="currentQuestionProgress"
>
<div>
<div class="pb-4 bg-white">
<article v-if="currentQuestion" class="overflow-hidden">
<div
class="h-2 bg-blue-900 transition-all duration-300 ease-out"
:style="{
width: `${(currentQuestionProgress / questionsCount) * 100}%`,
}"
/>
</div>
<div class="min-h-72 md:min-h-42">
<div class="px-4 mt-4 text-gray-600 flex">
<span> {{ currentQuestion.category }} </span>
<div class="max-md:hidden ms-auto text-nowrap" aria-hidden="true">
Frage {{ currentQuestionProgress }} / {{ questionsCount }}
</div>
class="bg-blue-200"
role="progressbar"
aria-label="Fortschritt"
aria-valuemin="1"
:aria-valuemax="questionsCount"
:aria-valuenow="currentQuestionProgress"
>
<div
class="h-2 bg-blue-900 motion-safe:transition-all duration-300 ease-out"
:style="{
width: `${(currentQuestionProgress / questionsCount) * 100}%`,
}"
/>
</div>
<div class="px-4">
<h2 class="my-8 text-xl md:text-2xl font-medium">
{{ currentQuestion.thesis }}
</h2>
</div>
</div>
<div class="px-4">
<div class="flex flex-col md:flex-row max-md:space-y-3 md:space-x-2">
<AnswerButton
answer="zu weit"
label="nein, geht mir zu weit"
:party-answer-exists="partyAnswerExists('zu weit')"
@save="saveAnswer"
accesskey="1"
>
<IconLess />
</AnswerButton>
<AnswerButton
answer="richtig"
label="ja, finde ich auch"
:party-answer-exists="partyAnswerExists('richtig')"
@save="saveAnswer"
accesskey="2"
<div class="min-h-72 md:min-h-42">
<Transition
class="motion-safe:transition-opacity duration-250 ease-linear"
enter-from-class="motion-safe:opacity-0"
leave-to-class="motion-safe:opacity-0"
mode="out-in"
>
<IconRight />
</AnswerButton>
<AnswerButton
answer="nicht weit genug"
label="nein, reicht mir nicht aus"
:party-answer-exists="partyAnswerExists('nicht weit genug')"
@save="saveAnswer"
accesskey="3"
<div
class="px-4 mt-4 text-gray-600 flex"
:key="currentQuestionIndex"
>
<span> {{ currentQuestion.category }} </span>
<div class="max-md:hidden ms-auto text-nowrap" aria-hidden="true">
Frage {{ currentQuestionProgress }} / {{ questionsCount }}
</div>
</div>
</Transition>
<Transition mode="out-in" :name="transitionName">
<div class="px-4" :key="currentQuestionIndex">
<h2 class="my-8 text-xl md:text-2xl font-medium">
{{ currentQuestion.thesis }}
</h2>
</div>
</Transition>
</div>
<Transition :name="transitionName" mode="out-in" class="px-4">
<div
class="flex flex-col md:flex-row max-md:space-y-3 md:space-x-2"
:key="currentQuestionIndex"
>
<IconMore />
</AnswerButton>
<div class="!ms-auto self-center max-md:pt-4">
<button @click="skipQuestion" class="btn-text">
These überspringen <IconForward class="ms-1" />
</button>
<AnswerButton
answer="zu weit"
label="nein, geht mir zu weit"
:party-answer-exists="partyAnswerExists('zu weit')"
@save="saveAnswer"
accesskey="1"
>
<IconLess />
</AnswerButton>
<AnswerButton
answer="richtig"
label="ja, finde ich auch"
:party-answer-exists="partyAnswerExists('richtig')"
@save="saveAnswer"
accesskey="2"
>
<IconRight />
</AnswerButton>
<AnswerButton
answer="nicht weit genug"
label="nein, reicht mir nicht aus"
:party-answer-exists="partyAnswerExists('nicht weit genug')"
@save="saveAnswer"
accesskey="3"
>
<IconMore />
</AnswerButton>
<div class="!ms-auto self-center max-md:pt-4">
<button @click="skipQuestion" class="btn-text">
These überspringen <IconForward class="ms-1" />
</button>
</div>
</div>
</div>
</div>
</article>
</div>

<div class="flex mt-4 text-blue-900">
<button @click="previousQuestion" class="btn-text">
<IconBack class="me-1" />
Zurück
</button>

<button
@click="emit('reset')"
class="btn-text ms-auto"
v-if="currentQuestionIndex > 0"
>
<IconRestart class="me-1" />
Neustarten
</button>
</Transition>
</article>
</div>
<div class="flex mt-4 text-blue-900">
<button @click="previousQuestion" class="btn-text">
<IconBack class="me-1" />
Zurück
</button>
<button
@click="emit('reset')"
class="btn-text ms-auto"
v-if="currentQuestionIndex > 0"
>
<IconRestart class="me-1" />
Neustarten
</button>
</div>
</div>
</template>
4 changes: 2 additions & 2 deletions src/components/Weights.vue
Original file line number Diff line number Diff line change
Expand Up @@ -56,12 +56,12 @@ const categories = computed(() => {
input[type='checkbox'] {
appearance: none;
@apply appearance-none relative w-8 h-8 border-2 border-gray-500 rounded-full transition-all duration-300 focus:ring focus:ring-blue-600 focus:border-blue-600 outline-transparent;
@apply appearance-none relative w-8 h-8 border-2 border-gray-500 rounded-full motion-safe:transition-all motion-safe:duration-300 focus:ring focus:ring-blue-600 focus:border-blue-600 outline-transparent;
}
input[type='checkbox']::before {
content: '';
@apply flex transition-opacity duration-150 opacity-0 absolute inset-0 text-lg rounded-full items-center justify-center text-white bg-gray-800;
@apply flex motion-safe:transition-opacity motion-safe:duration-150 opacity-0 absolute inset-0 text-lg rounded-full items-center justify-center text-white bg-gray-800;
background-image: url('data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSIyNCIgaGVpZ2h0PSIyNCIgdmlld0JveD0iMCAwIDI0IDI0Ij48cGF0aCBmaWxsPSIjZmZmIiBkPSJNMTAgMTdIN3EtLjgyNSAwLTEuNDEyLS41ODdUNSAxNXYtMnEwLS44MjUuNTg4LTEuNDEyVDcgMTFoMlY5SDZxLS40MjUgMC0uNzEyLS4yODhUNSA4dC4yODgtLjcxMlQ2IDdoM3EuODI1IDAgMS40MTMuNTg4VDExIDl2MnEwIC44MjUtLjU4NyAxLjQxM1Q5IDEzSDd2MmgzcS40MjUgMCAuNzEzLjI4OFQxMSAxNnQtLjI4OC43MTNUMTAgMTdtNi0zLjMyNWwtMS43NSAyLjlxLS4xMjUuMi0uMzEyLjMxM3QtLjQxMy4xMTJxLS41IDAtLjc2My0uNDM3dC4wMTMtLjg2M0wxNSAxMmwtMi4yMjUtMy43cS0uMjc1LS40MjUtLjAxMi0uODYyVDEzLjUyNSA3cS4yMjUgMCAuNDEzLjExM3QuMzEyLjMxMmwxLjc1IDIuOWwxLjc1LTIuOXEuMTI1LS4yLjMxMy0uMzEyVDE4LjQ3NSA3cS41IDAgLjc2My40Mzh0LS4wMTMuODYyTDE3IDEybDIuMjI1IDMuN3EuMjc1LjQyNS4wMTMuODYzdC0uNzYzLjQzN3EtLjIyNSAwLS40MTMtLjExMnQtLjMxMi0uMzEzeiIvPjwvc3ZnPg==');
background-size: 100%;
}
Expand Down
4 changes: 3 additions & 1 deletion src/pages/index.astro
Original file line number Diff line number Diff line change
Expand Up @@ -7,5 +7,7 @@ const questions = (await getCollection('questions')).map((q) => q.data)
---

<Layout>
<App questions={questions} client:only="vue" />
<div class="overflow-x-hidden">
<App questions={questions} client:only="vue" />
</div>
</Layout>

0 comments on commit a6b0c92

Please sign in to comment.