Upgraded all components to vue 3 & typescript

This commit is contained in:
2022-08-06 16:10:13 +02:00
parent 890d0c428d
commit d12dfc3c8e
34 changed files with 3508 additions and 3554 deletions

View File

@@ -4,49 +4,43 @@
</div>
</template>
<script>
export default {
data() {
return {
darkmode: this.systemDarkModeEnabled()
};
},
methods: {
toggleDarkmode() {
this.darkmode = !this.darkmode;
document.body.className = this.darkmode ? "dark" : "light";
},
systemDarkModeEnabled() {
const computedStyle = window.getComputedStyle(document.body);
if (computedStyle["colorScheme"] != null) {
return computedStyle.colorScheme.includes("dark");
}
return false;
}
},
computed: {
darkmodeToggleIcon() {
return this.darkmode ? "🌝" : "🌚";
}
<script setup lang="ts">
import { ref, computed } from "vue";
let darkmode = ref(systemDarkModeEnabled());
const darkmodeToggleIcon = computed(() => {
return darkmode.value ? "🌝" : "🌚";
});
function toggleDarkmode() {
darkmode.value = !darkmode.value;
document.body.className = darkmode.value ? "dark" : "light";
}
function systemDarkModeEnabled() {
const computedStyle = window.getComputedStyle(document.body);
if (computedStyle["colorScheme"] != null) {
return computedStyle.colorScheme.includes("dark");
}
return false;
}
};
</script>
<style lang="scss" scoped>
.darkToggle {
height: 25px;
width: 25px;
cursor: pointer;
position: fixed;
margin-bottom: 1.5rem;
margin-right: 2px;
bottom: 0;
right: 0;
z-index: 10;
.darkToggle {
height: 25px;
width: 25px;
cursor: pointer;
position: fixed;
margin-bottom: 1.5rem;
margin-right: 2px;
bottom: 0;
right: 0;
z-index: 10;
-webkit-user-select: none;
-moz-user-select: none;
-ms-user-select: none;
user-select: none;
}
-webkit-user-select: none;
-moz-user-select: none;
-ms-user-select: none;
user-select: none;
}
</style>

View File

@@ -10,73 +10,76 @@
</div>
</template>
<script>
import { mapGetters, mapActions } from "vuex";
<script setup lang="ts">
import { computed } from "vue";
import { useStore } from "vuex";
export default {
computed: { ...mapGetters("hamburger", ["isOpen"]) },
methods: { ...mapActions("hamburger", ["toggle"]) }
};
const store = useStore();
const isOpen = computed(() => store.getters["hamburger/isOpen"]);
const toggle = () => {
store.dispatch("hamburger/toggle");
};
</script>
<style lang="scss" scoped>
@import "src/scss/media-queries";
@import "src/scss/media-queries";
.nav__hamburger {
display: block;
position: relative;
width: var(--header-size);
height: var(--header-size);
cursor: pointer;
border-left: 1px solid var(--background-color);
background-color: var(--background-color-secondary);
.nav__hamburger {
display: block;
position: relative;
width: var(--header-size);
height: var(--header-size);
cursor: pointer;
border-left: 1px solid var(--background-color);
background-color: var(--background-color-secondary);
@include tablet-min {
display: none;
}
@include tablet-min {
display: none;
}
.bar {
position: absolute;
width: 23px;
height: 1px;
background-color: var(--text-color-70);
transition: all 300ms ease;
&:nth-child(1) {
left: 16px;
top: 17px;
}
&:nth-child(2) {
left: 16px;
top: 25px;
&:after {
content: "";
position: absolute;
left: 0px;
top: 0px;
width: 23px;
height: 1px;
transition: all 300ms ease;
}
}
&:nth-child(3) {
right: 15px;
top: 33px;
}
}
&.open {
.bar {
&:nth-child(1),
&:nth-child(3) {
width: 0;
position: absolute;
width: 23px;
height: 1px;
background-color: var(--text-color-70);
transition: all 300ms ease;
&:nth-child(1) {
left: 16px;
top: 17px;
}
&:nth-child(2) {
transform: rotate(-45deg);
left: 16px;
top: 25px;
&:after {
content: "";
position: absolute;
left: 0px;
top: 0px;
width: 23px;
height: 1px;
transition: all 300ms ease;
}
}
&:nth-child(2):after {
transform: rotate(-90deg);
background-color: var(--text-color-70);
&:nth-child(3) {
right: 15px;
top: 33px;
}
}
&.open {
.bar {
&:nth-child(1),
&:nth-child(3) {
width: 0;
}
&:nth-child(2) {
transform: rotate(-45deg);
}
&:nth-child(2):after {
transform: rotate(-90deg);
background-color: var(--text-color-70);
}
}
}
}
}
</style>

View File

@@ -1,48 +1,72 @@
<template>
<div class="loader">
<div :class="`loader type-${type}`">
<i class="loader--icon">
<i class="loader--icon-spinner" />
</i>
</div>
</template>
<!--
TODO: fetch and display movie facts after 1.5 seconds while loading?
--></template>
<script setup lang="ts">
import { defineProps } from "vue";
enum LoaderHeightType {
Page = "page",
Section = "section"
}
interface Props {
type?: LoaderHeightType;
}
const { type = LoaderHeightType.Page } = defineProps<Props>();
</script>
<style lang="scss" scoped>
@import "src/scss/variables";
@import "src/scss/variables";
.loader {
display: flex;
width: 100%;
height: 30vh;
justify-content: center;
align-items: center;
.loader {
display: flex;
width: 100%;
height: 30vh;
justify-content: center;
align-items: center;
&--icon {
border: 2px solid $text-color-70;
border-radius: 50%;
display: block;
height: 40px;
position: absolute;
width: 40px;
&.type-section {
height: 15vh;
}
&-spinner {
&--icon {
border: 2px solid $text-color-70;
border-radius: 50%;
display: block;
animation: load 1s linear infinite;
height: 35px;
width: 35px;
&:after {
border: 7px solid $green-90;
border-radius: 50%;
content: "";
left: 8px;
position: absolute;
top: 22px;
height: 40px;
position: absolute;
width: 40px;
&-spinner {
display: block;
animation: load 1s linear infinite;
height: 35px;
width: 35px;
&:after {
border: 7px solid $green-90;
border-radius: 50%;
content: "";
left: 8px;
position: absolute;
top: 22px;
}
}
}
@keyframes load {
100% {
transform: rotate(360deg);
}
}
}
@keyframes load {
100% {
transform: rotate(360deg);
}
}
}
</style>

View File

@@ -9,26 +9,18 @@
</div>
</template>
<script>
export default {
props: {
count: {
type: Number,
default: 1,
require: false
},
lineClass: {
type: String,
default: ""
},
top: {
type: Number,
default: 0
}
<script setup lang="ts">
import { defineProps } from "vue";
interface Props {
count?: Number;
lineClass?: String;
top?: Number;
}
};
const { count = 1, lineClass = "", top = 0 } = defineProps<Props>();
</script>
<style lang="scss" scoped>
@import "src/scss/loading-placeholder";
@import "src/scss/loading-placeholder";
</style>

View File

@@ -8,77 +8,71 @@
</button>
</template>
<script>
export default {
name: "seasonedButton",
props: {
active: {
type: Boolean,
default: false,
required: false
},
fullWidth: {
type: Boolean,
default: false,
required: false
}
},
methods: {
emit() {
this.$emit("click");
}
<script setup lang="ts">
import { ref, defineProps, defineEmits } from "vue";
import type { Ref } from "vue";
interface Props {
active?: Boolean;
fullWidth?: Boolean;
}
};
interface Emit {
(e: "click");
}
const props = defineProps<Props>();
const emit = defineEmits<Emit>();
</script>
<style lang="scss" scoped>
@import "src/scss/variables";
@import "src/scss/media-queries";
@import "src/scss/variables";
@import "src/scss/media-queries";
button {
display: inline-block;
border: 1px solid $text-color;
font-size: 11px;
font-weight: 300;
line-height: 1.5;
letter-spacing: 0.5px;
text-transform: uppercase;
min-height: 45px;
padding: 5px 10px 4px 10px;
margin: 0;
margin-right: 0.3rem;
color: $text-color;
background: $background-color-secondary;
cursor: pointer;
outline: none;
transition: background 0.5s ease, color 0.5s ease, border-color 0.5s ease;
button {
display: inline-block;
border: 1px solid $text-color;
font-size: 11px;
font-weight: 300;
line-height: 1.5;
letter-spacing: 0.5px;
text-transform: uppercase;
min-height: 45px;
padding: 5px 10px 4px 10px;
margin: 0;
margin-right: 0.3rem;
color: $text-color;
background: $background-color-secondary;
cursor: pointer;
outline: none;
transition: background 0.5s ease, color 0.5s ease, border-color 0.5s ease;
@include desktop {
font-size: 0.8rem;
padding: 6px 20px 5px 20px;
}
&.fullwidth {
font-size: 14px;
width: 40%;
@include mobile {
width: 60%;
@include desktop {
font-size: 0.8rem;
padding: 6px 20px 5px 20px;
}
}
&:focus,
&:active,
&.active {
background: $text-color;
color: $background-color;
}
&.fullwidth {
font-size: 14px;
width: 40%;
@media (hover: hover) {
&:hover {
@include mobile {
width: 60%;
}
}
&:focus,
&:active,
&.active {
background: $text-color;
color: $background-color;
}
@media (hover: hover) {
&:hover {
background: $text-color;
color: $background-color;
}
}
}
}
</style>

View File

@@ -1,134 +1,139 @@
<template>
<div class="group" :class="{ completed: value, focus }">
<div class="group" :class="{ completed: modelValue, focus }">
<component :is="inputIcon" v-if="inputIcon" />
<input
class="input"
:type="tempType || type"
@input="handleInput"
v-model="inputValue"
:type="toggledType || type"
:placeholder="placeholder"
@keyup.enter="event => $emit('enter', event)"
:value="modelValue"
@input="handleInput"
@keyup.enter="event => emit('enter', event)"
@focus="focus = true"
@blur="focus = false"
/>
<i
v-if="value && type === 'password'"
v-if="modelValue && type === 'password'"
@click="toggleShowPassword"
@keydown.enter="toggleShowPassword"
class="show noselect"
tabindex="0"
>{{ tempType == "password" ? "show" : "hide" }}</i
>{{ toggledType == "password" ? "show" : "hide" }}</i
>
</div>
</template>
<script>
import IconKey from "../../icons/IconKey";
import IconEmail from "../../icons/IconEmail";
<script setup lang="ts">
import { ref, computed, defineProps, defineEmits } from "vue";
import IconKey from "@/icons/IconKey.vue";
import IconEmail from "@/icons/IconEmail.vue";
import IconBinoculars from "@/icons/IconBinoculars.vue";
import type { Ref } from "vue";
export default {
components: { IconKey, IconEmail },
props: {
placeholder: { type: String },
type: { type: String, default: "text" },
value: { type: String, default: undefined }
},
data() {
return {
inputValue: this.value || undefined,
tempType: this.type,
focus: false
};
},
computed: {
inputIcon() {
if (this.type === "password") return IconKey;
if (this.type === "email") return IconEmail;
return false;
}
},
methods: {
handleInput(event) {
if (this.value !== undefined) {
this.$emit("update:value", this.inputValue);
} else {
this.$emit("change", this.inputValue, event);
}
},
toggleShowPassword() {
if (this.tempType === "text") {
this.tempType = "password";
} else {
this.tempType = "text";
}
interface Props {
modelValue: string;
placeholder: string;
type?: string;
}
interface Emit {
(e: "change");
(e: "enter");
(e: "update:modelValue", value: string);
}
const { placeholder, type = "text", modelValue } = defineProps<Props>();
const emit = defineEmits<Emit>();
const toggledType: Ref<string> = ref(type);
const focus: Ref<boolean> = ref(false);
const inputIcon = computed(() => {
if (type === "password") return IconKey;
if (type === "email") return IconEmail;
if (type === "torrents") return IconBinoculars;
return false;
});
function handleInput(event: KeyboardEvent) {
const target = event?.target as HTMLInputElement;
if (!target) return;
emit("update:modelValue", target?.value);
}
// Could we move this to component that injects ??
function toggleShowPassword() {
if (toggledType.value === "text") {
toggledType.value = "password";
} else {
toggledType.value = "text";
}
}
};
</script>
<style lang="scss" scoped>
@import "src/scss/variables";
@import "src/scss/media-queries";
@import "src/scss/variables";
@import "src/scss/media-queries";
.group {
display: flex;
width: 100%;
position: relative;
max-width: 35rem;
border: 1px solid var(--text-color-50);
background-color: var(--background-color-secondary);
.group {
display: flex;
width: 100%;
position: relative;
max-width: 35rem;
border: 1px solid var(--text-color-50);
background-color: var(--background-color-secondary);
&.completed,
&.focus,
&:hover,
&:focus {
border-color: var(--text-color);
&.completed,
&.focus,
&:hover,
&:focus {
border-color: var(--text-color);
svg {
fill: var(--text-color);
}
}
svg {
fill: var(--text-color);
width: 24px;
height: 24px;
fill: var(--text-color-50);
pointer-events: none;
margin-top: 10px;
margin-left: 10px;
z-index: 8;
}
input {
width: 100%;
padding: 10px;
outline: none;
background-color: var(--background-color-secondary);
color: var(--text-color);
font-weight: 100;
font-size: 1.2rem;
margin: 0;
z-index: 3;
border: none;
border-radius: 0;
-webkit-appearance: none;
}
.show {
position: absolute;
display: grid;
place-items: center;
right: 20px;
z-index: 11;
margin: auto 0;
height: 100%;
font-size: 0.9rem;
cursor: pointer;
color: var(--text-color-50);
-webkit-user-select: none;
user-select: none;
}
}
svg {
width: 24px;
height: 24px;
fill: var(--text-color-50);
pointer-events: none;
margin-top: 10px;
margin-left: 10px;
z-index: 8;
}
input {
width: 100%;
padding: 10px;
outline: none;
background-color: var(--background-color-secondary);
color: var(--text-color);
font-weight: 100;
font-size: 1.2rem;
margin: 0;
z-index: 3;
border: none;
border-radius: 0;
-webkit-appearance: none;
}
.show {
position: absolute;
display: grid;
place-items: center;
right: 20px;
z-index: 11;
margin: auto 0;
height: 100%;
font-size: 0.9rem;
cursor: pointer;
color: var(--text-color-50);
-webkit-user-select: none;
user-select: none;
}
}
</style>

View File

@@ -1,13 +1,13 @@
<template>
<transition-group name="fade">
<div
class="message"
v-for="(message, index) in reversedMessages"
class="card"
v-for="(message, index) in messages"
:key="`${index}-${message.title}-${message.type}}`"
:class="message.type || 'warning'"
>
<span class="pinstripe"></span>
<div>
<div class="content">
<h2 class="title">
{{ message.title || defaultTitles[message.type] }}
</h2>
@@ -16,150 +16,149 @@
}}</span>
</div>
<button class="dismiss" @click="clicked(message)">X</button>
<button class="dismiss" @click="dismiss(index)">X</button>
</div>
</transition-group>
</template>
<script>
export default {
props: {
messages: {
required: true,
type: Array
}
},
data() {
return {
defaultTitles: {
error: "Unexpected error",
warning: "Something went wrong",
undefined: "Something went wrong"
},
localMessages: [...this.messages]
};
},
computed: {
reversedMessages() {
return [...this.messages].reverse();
}
},
methods: {
clicked(e) {
const removedMessage = [...this.messages].filter(mes => mes !== e);
this.$emit("update:messages", removedMessage);
}
<script setup lang="ts">
import { defineProps, defineEmits } from "vue";
import type { Ref } from "vue";
interface IErrorMessage {
title: string;
message: string;
type: "error" | "success" | "warning";
}
interface Props {
messages: IErrorMessage[];
}
interface Emit {
(e: "update:messages", messages: IErrorMessage[]);
}
const props = defineProps<Props>();
const emit = defineEmits<Emit>();
const defaultTitles = {
error: "Unexpected error",
warning: "Something went wrong",
success: "",
undefined: "Something went wrong"
};
function dismiss(index: number) {
props.messages.splice(index, 1);
emit("update:messages", [...props.messages]);
}
};
</script>
<style lang="scss" scoped>
@import "src/scss/variables";
@import "src/scss/media-queries";
@import "src/scss/variables";
@import "src/scss/media-queries";
.fade-enter-active {
transition: opacity 0.4s;
}
.fade-leave-active {
transition: opacity 0.1s;
}
.fade-enter, .fade-leave-to /* .fade-leave-active below version 2.1.8 */ {
opacity: 0;
}
.fade-active {
transition: opacity 0.4s;
}
.fade-enter, .fade-leave-to /* .fade-leave-active below version 2.1.8 */ {
opacity: 0;
}
.message {
width: 100%;
max-width: 35rem;
display: flex;
margin-top: 1rem;
margin-bottom: 1rem;
color: $text-color-70;
> div {
margin: 10px 24px;
.card {
width: 100%;
}
max-width: 35rem;
.title {
font-weight: 300;
letter-spacing: 0.25px;
margin: 0;
font-size: 1.3rem;
color: $text-color;
transition: color 0.5s ease;
}
.message {
font-weight: 300;
display: flex;
margin-top: 0.8rem;
color: $text-color-70;
transition: color 0.5s ease;
margin: 0.2rem 0 0.5rem;
}
@include mobile-only {
> div {
margin: 6px 6px;
line-height: 1.3rem;
.content {
margin: 0.4rem 1.2rem;
width: 100%;
.title {
font-weight: 300;
letter-spacing: 0.25px;
margin: 0;
font-size: 1.3rem;
color: $text-color;
transition: color 0.5s ease;
}
.message {
font-weight: 400;
font-size: 1.2rem;
color: $text-color-70;
transition: color 0.5s ease;
margin-bottom: 0.2rem;
}
@include mobile-only {
margin: 6px 6px;
line-height: 1.3rem;
h2 {
font-size: 1.1rem;
}
span {
font-size: 0.9rem;
}
}
}
h2 {
font-size: 1.1rem;
}
span {
font-size: 0.9rem;
}
}
.pinstripe {
width: 0.5rem;
background-color: $color-error-highlight;
}
.dismiss {
position: relative;
-webkit-appearance: none;
-moz-appearance: none;
background-color: transparent;
border: unset;
font-size: 18px;
cursor: pointer;
top: 0;
float: right;
height: 1.5rem;
width: 1.5rem;
padding: 0;
margin-top: 0.5rem;
margin-right: 0.5rem;
color: $text-color-70;
transition: color 0.5s ease;
&:hover {
color: $text-color;
}
}
&.success {
background-color: $color-success;
.pinstripe {
background-color: $color-success-highlight;
}
}
&.error {
background-color: $color-error;
.pinstripe {
width: 0.5rem;
background-color: $color-error-highlight;
}
}
&.warning {
background-color: $color-warning;
.dismiss {
position: relative;
-webkit-appearance: none;
-moz-appearance: none;
background-color: transparent;
border: unset;
font-size: 18px;
cursor: pointer;
.pinstripe {
background-color: $color-warning-highlight;
top: 0;
float: right;
height: 1.5rem;
width: 1.5rem;
padding: 0;
margin-top: 0.5rem;
margin-right: 0.5rem;
color: $text-color-70;
transition: color 0.5s ease;
&:hover {
color: $text-color;
}
}
&.success {
background-color: $color-success;
.pinstripe {
background-color: $color-success-highlight;
}
}
&.error {
background-color: $color-error;
.pinstripe {
background-color: $color-error-highlight;
}
}
&.warning {
background-color: $color-warning;
.pinstripe {
background-color: $color-warning-highlight;
}
}
}
}
</style>

View File

@@ -4,83 +4,74 @@
v-for="option in options"
:key="option"
class="toggle-button"
@click="toggle(option)"
:class="toggleValue === option ? 'selected' : null"
@click="toggleTo(option)"
:class="selected === option ? 'selected' : null"
>
{{ option }}
</button>
</div>
</template>
<script>
export default {
props: {
options: {
Array,
required: true
},
selected: {
type: String,
required: false,
default: undefined
}
},
data() {
return {
toggleValue: this.selected || this.options[0]
};
},
methods: {
toggle(toggleValue) {
this.toggleValue = toggleValue;
if (this.selected !== undefined) {
this.$emit("update:selected", toggleValue);
this.$emit("change", toggleValue);
} else {
this.$emit("change", toggleValue);
}
}
<script setup lang="ts">
import { ref, defineProps, defineEmits } from "vue";
import type { Ref } from "vue";
interface Props {
options: string[];
selected?: string;
}
interface Emit {
(e: "update:selected", selected: string);
(e: "change");
}
defineProps<Props>();
const emit = defineEmits<Emit>();
function toggleTo(option: string) {
emit("update:selected", option);
emit("change");
}
};
</script>
<style lang="scss" scoped>
@import "src/scss/variables";
@import "src/scss/variables";
$background: $background-ui;
$background-selected: $background-color-secondary;
$background: $background-ui;
$background-selected: $background-color-secondary;
.toggle-container {
width: 100%;
display: flex;
overflow-x: scroll;
flex-direction: row;
justify-content: center;
align-items: center;
background-color: $background;
border: 2px solid $background;
border-radius: 8px;
border-left: 4px solid $background;
border-right: 4px solid $background;
.toggle-button {
font-size: 1rem;
line-height: 1rem;
font-weight: normal;
padding: 0.5rem;
border: 0;
color: $text-color;
.toggle-container {
width: 100%;
display: flex;
overflow-x: scroll;
flex-direction: row;
justify-content: center;
align-items: center;
background-color: $background;
text-transform: capitalize;
cursor: pointer;
display: block;
flex: 1 0 auto;
border: 2px solid $background;
border-radius: 8px;
border-left: 4px solid $background;
border-right: 4px solid $background;
&.selected {
.toggle-button {
font-size: 1rem;
line-height: 1rem;
font-weight: normal;
padding: 0.5rem;
border: 0;
color: $text-color;
background-color: $background-selected;
border-radius: 8px;
background-color: $background;
text-transform: capitalize;
cursor: pointer;
display: block;
flex: 1 0 auto;
&.selected {
color: $text-color;
background-color: $background-selected;
border-radius: 8px;
}
}
}
}
</style>