Transferred and translated the frontend code from L10 to L12
linter / quality (push) Waiting to run
tests / ci (push) Waiting to run
linter / quality (push) Waiting to run
tests / ci (push) Waiting to run
This commit is contained in:
@@ -0,0 +1,33 @@
|
||||
<script setup>
|
||||
import { defineProps } from 'vue';
|
||||
|
||||
const props = defineProps({
|
||||
block: {
|
||||
type: Object,
|
||||
required: true,
|
||||
},
|
||||
});
|
||||
|
||||
const faqs = props.block.faqs || [
|
||||
{ question: 'Hva er dette?', answer: 'Dette er et eksempel på et spørsmål og svar.' },
|
||||
{ question: 'Hvordan fungerer det?', answer: 'Du legger til innhold og publiserer siden.' },
|
||||
];
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<section class="relative py-20 md:py-24 overflow-hidden mx-auto max-w-[1280px] bg-black">
|
||||
<div class="container mx-auto px-6 lg:px-20">
|
||||
<h2 class="text-amber-600 text-xl md:text-2xl font-serif mb-6">{{ block.title || 'Vanlige spørsmål' }}</h2>
|
||||
<div class="space-y-4">
|
||||
<details v-for="(faq, index) in faqs" :key="index" class="bg-gray-700 p-4 rounded-md shadow">
|
||||
<summary class="cursor-pointer font-medium text-white">{{ faq.question }}</summary>
|
||||
<p class="mt-2 text-gray-300">{{ faq.answer }}</p>
|
||||
</details>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
</template>
|
||||
|
||||
<style scoped>
|
||||
/* Optional collapse styles */
|
||||
</style>
|
||||
@@ -0,0 +1,103 @@
|
||||
<script setup>
|
||||
import Icon from '@/Components/Icon.vue';
|
||||
import { toRef, ref } from 'vue';
|
||||
|
||||
const props = defineProps({
|
||||
block: {
|
||||
type: Object,
|
||||
required: true,
|
||||
},
|
||||
});
|
||||
|
||||
const emit = defineEmits(['update:block']);
|
||||
const local = toRef(props, 'block');
|
||||
|
||||
// Drag state
|
||||
const draggedIndex = ref(null);
|
||||
|
||||
function handleDragStart(event, index) {
|
||||
draggedIndex.value = index;
|
||||
event.dataTransfer.effectAllowed = 'move';
|
||||
event.dataTransfer.setData('text/plain', index);
|
||||
};
|
||||
|
||||
function handleDragOver(event) {
|
||||
event.preventDefault();
|
||||
event.dataTransfer.dropEffect = 'move';
|
||||
};
|
||||
|
||||
function handleDrop(event, index) {
|
||||
event.preventDefault();
|
||||
const fromIndex = draggedIndex.value;
|
||||
if (fromIndex === null || fromIndex === index) return;
|
||||
|
||||
const item = local.value.faqs.splice(fromIndex, 1)[0];
|
||||
local.value.faqs.splice(index, 0, item);
|
||||
draggedIndex.value = null;
|
||||
};
|
||||
|
||||
function addFaq() {
|
||||
local.value.faqs.push({
|
||||
question: '',
|
||||
answer: '',
|
||||
});
|
||||
}
|
||||
|
||||
function removeFaq(index) {
|
||||
local.value.faqs.splice(index, 1);
|
||||
}
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div class="space-y-4">
|
||||
<div>
|
||||
<label class="block text-sm font-medium text-gray-700 dark:text-gray-300">Tittel for seksjonen</label>
|
||||
<input
|
||||
type="text"
|
||||
class="mt-1 block w-full rounded-md border-gray-300 dark:border-gray-600 shadow-sm focus:border-blue-500 focus:ring-blue-500 dark:bg-gray-800 dark:text-white"
|
||||
v-model="local.title"
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div class="space-y-4">
|
||||
<div
|
||||
v-for="(faq, index) in local.faqs"
|
||||
:key="index"
|
||||
class="flex items-start gap-2 p-4 rounded border dark:border-gray-600 bg-gray-50 dark:bg-gray-800"
|
||||
draggable="true"
|
||||
@dragstart="handleDragStart($event, index)"
|
||||
@dragover="handleDragOver"
|
||||
@drop="handleDrop($event, index)"
|
||||
>
|
||||
<div class="cursor-move mt-1 text-gray-500 dark:text-gray-400">
|
||||
<Icon name="move" class="w-4 h-4" />
|
||||
</div>
|
||||
|
||||
<div class="flex-1 space-y-2">
|
||||
<div class="flex justify-between items-center">
|
||||
<span class="text-sm font-semibold text-gray-700 dark:text-gray-300">FAQ {{ index + 1 }}</span>
|
||||
<button @click="removeFaq(index)" class="text-red-500 hover:underline text-xs">Fjern</button>
|
||||
</div>
|
||||
|
||||
<div class="mb-2">
|
||||
<label class="block text-xs font-medium text-gray-600 dark:text-gray-400">Spørsmål</label>
|
||||
<input type="text" class="mt-1 block w-full rounded-md border-gray-300 dark:border-gray-600 shadow-sm focus:border-blue-500 focus:ring-blue-500 dark:bg-gray-800 dark:text-white" v-model="faq.question" />
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<label class="block text-xs font-medium text-gray-600 dark:text-gray-400">Svar</label>
|
||||
<textarea rows="2" class="mt-1 block w-full rounded-md border-gray-300 dark:border-gray-600 shadow-sm focus:border-blue-500 focus:ring-blue-500 dark:bg-gray-800 dark:text-white" v-model="faq.answer"></textarea>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<button @click="addFaq" class="mt-2 px-4 py-2 bg-blue-600 text-white text-sm rounded hover:bg-blue-700">
|
||||
Legg til ny FAQ
|
||||
</button>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<style scoped>
|
||||
|
||||
</style>
|
||||
@@ -0,0 +1,33 @@
|
||||
<script setup>
|
||||
import { defineProps } from 'vue';
|
||||
|
||||
const props = defineProps({
|
||||
block: {
|
||||
type: Object,
|
||||
required: true,
|
||||
},
|
||||
});
|
||||
|
||||
const faqs = props.block.faqs || [
|
||||
{ question: 'Hva er dette?', answer: 'Dette er et eksempel på et spørsmål og svar.' },
|
||||
{ question: 'Hvordan fungerer det?', answer: 'Du legger til innhold og publiserer siden.' },
|
||||
];
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<section class="relative py-20 md:py-24 overflow-hidden mx-auto max-w-[1280px] bg-black">
|
||||
<div class="container mx-auto px-6 lg:px-20">
|
||||
<h2 class="text-amber-600 text-xl md:text-2xl font-serif mb-6">{{ block.title || 'Vanlige spørsmål' }}</h2>
|
||||
<div class="space-y-4">
|
||||
<details v-for="(faq, index) in faqs" :key="index" class="bg-gray-700 p-4 rounded-md shadow">
|
||||
<summary class="cursor-pointer font-medium text-white">{{ faq.question }}</summary>
|
||||
<p class="mt-2 text-gray-300">{{ faq.answer }}</p>
|
||||
</details>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
</template>
|
||||
|
||||
<style scoped>
|
||||
/* Optional collapse styles */
|
||||
</style>
|
||||
@@ -0,0 +1,117 @@
|
||||
<script setup>
|
||||
import { defineProps, ref, nextTick, onMounted } from 'vue';
|
||||
import { gsap } from 'gsap';
|
||||
import { initFlowbite } from 'flowbite';
|
||||
import { marked } from 'marked';
|
||||
|
||||
onMounted(() => {
|
||||
initFlowbite();
|
||||
faqs[0].categoryContent[0].state = true;
|
||||
});
|
||||
|
||||
const faqArticleContainer = ref(null);
|
||||
|
||||
const props = defineProps({
|
||||
block: {
|
||||
type: Object,
|
||||
required: true,
|
||||
},
|
||||
});
|
||||
|
||||
const faqs = props.block.faqs || [
|
||||
{
|
||||
categoryName: 'Kategori 1',
|
||||
categoryContent: [
|
||||
{ question: 'Spørsmål 1', answer: 'Dette er svar på spørsmål 1', state: false },
|
||||
{ question: 'Spørsmål 2', answer: 'Dette er svar på spørsmål 2', state: false },
|
||||
{ question: 'Spørsmål 3', answer: 'Dette er svar på spørsmål 3', state: false }
|
||||
]
|
||||
},
|
||||
{
|
||||
categoryName: 'Kategori 2',
|
||||
categoryContent: [
|
||||
{ question: 'Spørsmål 4', answer: 'Dette er svar på spørsmål 4', state: false },
|
||||
{ question: 'Spørsmål 5', answer: 'Dette er svar på spørsmål 5', state: false },
|
||||
{ question: 'Spørsmål 6', answer: 'Dette er svar på spørsmål 6', state: false }
|
||||
]
|
||||
}
|
||||
];
|
||||
|
||||
const setFalseExcept = (categoryIndex, qaIndex) => {
|
||||
faqs.forEach((category, cIdx) => {
|
||||
category.categoryContent.forEach((qa, qIdx) => {
|
||||
qa.state = cIdx === categoryIndex && qIdx === qaIndex;
|
||||
});
|
||||
});
|
||||
};
|
||||
|
||||
const showFaq = (categoryIndex, qaIndex) => {
|
||||
gsap.to(faqArticleContainer.value, {y: 200, opacity: 0, onComplete: () => {
|
||||
setFalseExcept(categoryIndex, qaIndex);
|
||||
// Wait for the DOM to update
|
||||
nextTick().then(() => {
|
||||
// Animate new articles in from the right
|
||||
gsap.fromTo(faqArticleContainer.value, {y: 200, opacity: 0}, {y: 0, opacity: 1});
|
||||
});
|
||||
}});
|
||||
};
|
||||
|
||||
const stateOf = (categoryIndex, qaIndex) => {
|
||||
return faqs[categoryIndex].categoryContent[qaIndex]?.state;
|
||||
};
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<section class="relative py-20 md:py-24 overflow-hidden mx-auto max-w-[1280px] bg-black">
|
||||
<div class="relative container px-4 mx-auto">
|
||||
<div class="max-w-7xl mx-auto">
|
||||
<div class="text-center mb-20">
|
||||
<span v-if="block.showlabel" class="inline-block py-1 px-3 mb-4 text-xs font-semibold text-orange-900 bg-orange-50 rounded-full">
|
||||
{{ block.label || 'Skriv inn en tekst for merkelappen' }}
|
||||
</span>
|
||||
<h1 v-if="block.showtitle" class="font-heading text-5xl xs:text-6xl md:text-7xl font-bold text-gray-500">
|
||||
<span>{{ block.title || 'Overskrift' }} </span>
|
||||
<span class="font-serif italic font-normal">{{ block.titlecursive || 'Overskriftkursiv' }}</span>
|
||||
</h1>
|
||||
</div>
|
||||
<div class="flex flex-wrap -mx-4 -mb-8">
|
||||
<div class="w-full lg:w-1/3 px-4 mb-15 lg:mb-0">
|
||||
<div class="flex flex-wrap -mx-2 lg:flex-col lg:max-w-sm border-b lg:border-b-0 lg:border-r border-gray-100">
|
||||
<div v-for="(faq, index) in faqs" :key="index" class="w-full md:w-1/2 lg:w-full px-2 mb-15">
|
||||
<h4 class="text-2xl font-semibold mb-6 text-amber-600">{{ faq.categoryName }}</h4>
|
||||
<ul class="mb-6">
|
||||
<li v-for="(category, catIndex) in faq.categoryContent" :key="catIndex" class="mb-6">
|
||||
<button type="button" @click="showFaq(index, catIndex)" class="flex items-center text-lg hover:text-amber-700" :class="stateOf(index, catIndex) ? 'text-amber-700 font-semibold' : 'text-white font-normal'">
|
||||
<svg width="12" height="12" viewbox="0 0 12 12" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<circle cx="6" cy="6" r="5" stroke="#C3C6CE" stroke-width="2"></circle>
|
||||
</svg>
|
||||
<span class="ml-3">
|
||||
{{ category.question }}
|
||||
</span>
|
||||
</button>
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="w-full lg:w-2/3 px-4" ref="faqArticleContainer">
|
||||
<template v-for="(faq, index) in faqs" :key="index">
|
||||
<template v-for="(category, catIndex) in faq.categoryContent" :key="catIndex">
|
||||
<div v-show="stateOf(index, catIndex)" class="max-w-xl xl:max-w-3xl mx-auto lg:mr-0">
|
||||
<h2 class="text-5xl font-semibold text-gray-500 mb-14">{{ category.question }}</h2>
|
||||
<div v-if="category.answer" v-html="marked(category.answer)" class="text-gray-300">
|
||||
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
</template>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
</template>
|
||||
|
||||
<style scoped>
|
||||
/* Optional FAQ list styles */
|
||||
</style>
|
||||
@@ -0,0 +1,285 @@
|
||||
<script setup>
|
||||
import Icon from '@/Components/Icon.vue'
|
||||
import { ref, toRef, watch } from 'vue';
|
||||
|
||||
const props = defineProps({
|
||||
block: { type: Object, required: true }
|
||||
});
|
||||
|
||||
const emit = defineEmits(['update:block']);
|
||||
|
||||
const local = toRef(props, 'block');
|
||||
const collapsedCategories = ref([]);
|
||||
|
||||
watch(local, (val) => {
|
||||
emit('update:block', val);
|
||||
}, { deep: true });
|
||||
|
||||
watch(() => local.value.faqs, (faqs) => {
|
||||
if (collapsedCategories.value.length !== faqs.length) {
|
||||
collapsedCategories.value = faqs.map((_, i) =>
|
||||
collapsedCategories.value[i] ?? true
|
||||
);
|
||||
}
|
||||
}, { immediate: true, deep: true });
|
||||
|
||||
function toggleCategoryCollapse(index) {
|
||||
collapsedCategories.value[index] = !collapsedCategories.value[index];
|
||||
};
|
||||
|
||||
|
||||
// Drag state for categories
|
||||
const draggedCategoryIndex = ref(null);
|
||||
|
||||
function handleDragStartCategory(event, index) {
|
||||
draggedCategoryIndex.value = index;
|
||||
event.dataTransfer.effectAllowed = 'move';
|
||||
event.dataTransfer.setData('text/plain', index);
|
||||
};
|
||||
|
||||
function handleDragOverCategory(event) {
|
||||
event.preventDefault();
|
||||
event.dataTransfer.dropEffect = 'move';
|
||||
};
|
||||
|
||||
function handleDropCategory(event, index) {
|
||||
event.preventDefault();
|
||||
const fromIndex = draggedCategoryIndex.value;
|
||||
if (fromIndex === null || fromIndex === index) return;
|
||||
|
||||
const item = local.value.faqs.splice(fromIndex, 1)[0];
|
||||
local.value.faqs.splice(index, 0, item);
|
||||
draggedCategoryIndex.value = null;
|
||||
};
|
||||
|
||||
// End drag state categories
|
||||
|
||||
// Add and remove questions
|
||||
function addFaqCategory() {
|
||||
local.value.faqs = local.value.faqs || [];
|
||||
local.value.faqs.push({ categoryName: '', categoryContent: [{}] });
|
||||
};
|
||||
|
||||
function removeFaqCategory(index) {
|
||||
local.value.faqs.splice(index, 1);
|
||||
};
|
||||
// End add and remove categories
|
||||
|
||||
|
||||
// Drag state for questions & answers
|
||||
const draggedQAIndex = ref(null);
|
||||
const draggedCategoryForQA = ref(null);
|
||||
|
||||
function handleDragStartQA(event, indexQA, categoryIndex) {
|
||||
draggedQAIndex.value = indexQA;
|
||||
draggedCategoryForQA.value = categoryIndex;
|
||||
event.dataTransfer.effectAllowed = 'move';
|
||||
event.dataTransfer.setData('text/plain', indexQA);
|
||||
}
|
||||
|
||||
function handleDragOverQA(event) {
|
||||
event.preventDefault();
|
||||
event.dataTransfer.dropEffect = 'move';
|
||||
}
|
||||
|
||||
function handleDropQA(event, indexQA, categoryIndex) {
|
||||
event.preventDefault();
|
||||
|
||||
const fromIndex = draggedQAIndex.value;
|
||||
const fromCategoryIndex = draggedCategoryForQA.value;
|
||||
|
||||
if (
|
||||
fromIndex === null ||
|
||||
fromCategoryIndex === null ||
|
||||
(fromIndex === indexQA && fromCategoryIndex === categoryIndex)
|
||||
) return;
|
||||
|
||||
const sourceCategory = local.value.faqs[fromCategoryIndex];
|
||||
const targetCategory = local.value.faqs[categoryIndex];
|
||||
|
||||
const item = sourceCategory.categoryContent.splice(fromIndex, 1)[0];
|
||||
targetCategory.categoryContent.splice(indexQA, 0, item);
|
||||
|
||||
draggedQAIndex.value = null;
|
||||
draggedCategoryForQA.value = null;
|
||||
}
|
||||
// End drag state for questions & answers
|
||||
|
||||
// Add and remove questions & answers
|
||||
function addQA(categoryIndex) {
|
||||
local.value.faqs[categoryIndex].categoryContent.push({ question: '', answer: '', state: false });
|
||||
}
|
||||
|
||||
function removeQA(indexQA, categoryIndex) {
|
||||
local.value.faqs[categoryIndex].categoryContent.splice(indexQA, 1);
|
||||
}
|
||||
// End add and remove
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div class="space-y-4">
|
||||
<div class="mb-2">
|
||||
<label class="block text-sm font-medium text-gray-700 dark:text-gray-300">DOM ID</label>
|
||||
<input
|
||||
type="text"
|
||||
class="mt-1 block w-full rounded-md border-gray-300 dark:border-gray-600 shadow-sm focus:border-blue-500 focus:ring-blue-500 dark:bg-gray-800 dark:text-white"
|
||||
v-model="local.anchorName"
|
||||
/>
|
||||
</div>
|
||||
<div class="mb-2">
|
||||
<div class="flex">
|
||||
<div class="flex items-center h-5">
|
||||
<input
|
||||
id="label-checkbox"
|
||||
aria-describedby="label-checkbox-text"
|
||||
type="checkbox"
|
||||
v-model="local.showlabel"
|
||||
class="w-4 h-4 text-blue-600 bg-gray-100 border-gray-300 rounded-sm focus:ring-blue-500 dark:focus:ring-blue-600 dark:ring-offset-gray-800 focus:ring-2 dark:bg-gray-700 dark:border-gray-600"
|
||||
>
|
||||
</div>
|
||||
<div class="ms-2 text-sm">
|
||||
<label for="label-checkbox" class="font-medium text-gray-900 dark:text-gray-300">Vis merkelapp</label>
|
||||
<p id="label-checkbox-text" class="text-xs font-normal text-gray-500 dark:text-gray-300">Skru merkelappen øverst på komponenten av, eller på</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="mb-2">
|
||||
<label class="block text-sm font-medium text-gray-700 dark:text-gray-300">Merkelapp tekst</label>
|
||||
<input
|
||||
type="text"
|
||||
class="mt-1 block w-full rounded-md border-gray-300 dark:border-gray-600 shadow-sm focus:border-blue-500 focus:ring-blue-500 dark:bg-gray-800 dark:text-white"
|
||||
v-model="local.label"
|
||||
:disabled="!local.showlabel"
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div class="mb-2">
|
||||
<div class="flex">
|
||||
<div class="flex items-center h-5">
|
||||
<input
|
||||
id="title-checkbox"
|
||||
aria-describedby="title-checkbox-text"
|
||||
type="checkbox"
|
||||
v-model="local.showtitle"
|
||||
class="w-4 h-4 text-blue-600 bg-gray-100 border-gray-300 rounded-sm focus:ring-blue-500 dark:focus:ring-blue-600 dark:ring-offset-gray-800 focus:ring-2 dark:bg-gray-700 dark:border-gray-600"
|
||||
>
|
||||
</div>
|
||||
<div class="ms-2 text-sm">
|
||||
<label for="title-checkbox" class="font-medium text-gray-900 dark:text-gray-300">Vis tittel</label>
|
||||
<p id="title-checkbox-text" class="text-xs font-normal text-gray-500 dark:text-gray-300">Skru tittelen på komponenten av, eller på</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="mb-2">
|
||||
<label class="block text-sm font-medium text-gray-700 dark:text-gray-300">Tittel tekst</label>
|
||||
<input
|
||||
type="text"
|
||||
class="mt-1 block w-full rounded-md border-gray-300 dark:border-gray-600 shadow-sm focus:border-blue-500 focus:ring-blue-500 dark:bg-gray-800 dark:text-white"
|
||||
v-model="local.title"
|
||||
:disabled="!local.showtitle"
|
||||
/>
|
||||
</div>
|
||||
<div class="mb-2">
|
||||
<label class="block text-sm font-medium text-gray-700 dark:text-gray-300">Tittel kursiv tekst</label>
|
||||
<input
|
||||
type="text"
|
||||
class="mt-1 block w-full rounded-md border-gray-300 dark:border-gray-600 shadow-sm focus:border-blue-500 focus:ring-blue-500 dark:bg-gray-800 dark:text-white"
|
||||
v-model="local.titlecursive"
|
||||
:disabled="!local.showtitle"
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div class="space-y-4">
|
||||
<div
|
||||
v-for="(faq, index) in local.faqs"
|
||||
:key="index"
|
||||
class="flex items-start gap-2 p-4 rounded border dark:border-gray-600 bg-gray-50 dark:bg-gray-800"
|
||||
draggable="true"
|
||||
@dragstart="handleDragStartCategory($event, index)"
|
||||
@dragover="handleDragOverCategory"
|
||||
@drop="handleDropCategory($event, index)"
|
||||
>
|
||||
<!-- Drag handle -->
|
||||
<div class="cursor-move mt-1 text-gray-500 dark:text-gray-400">
|
||||
<Icon name="move" class="w-4 h-4" />
|
||||
</div>
|
||||
|
||||
<!-- Content -->
|
||||
<div class="flex-1 space-y-2">
|
||||
<div class="flex justify-between items-center">
|
||||
<span class="text-sm font-semibold text-gray-700 dark:text-gray-300">FAQ Kategori {{ index + 1 }}</span>
|
||||
<div class="flex items-center gap-2">
|
||||
<button
|
||||
@click="toggleCategoryCollapse(index)"
|
||||
class="text-blue-500 text-xs hover:underline"
|
||||
>
|
||||
{{ collapsedCategories[index] ? 'Vis' : 'Skjul' }}
|
||||
</button>
|
||||
<button @click="removeFaqCategory(index)" class="text-red-500 hover:underline text-xs">
|
||||
Fjern
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
<div class="mb-2">
|
||||
<label class="block text-xs font-medium text-gray-600 dark:text-gray-400">Kategori tittel</label>
|
||||
<input type="text" class="block w-full rounded-md border-gray-300 dark:border-gray-600 shadow-sm focus:border-blue-500 focus:ring-blue-500 dark:bg-gray-800 dark:text-white" v-model="faq.categoryName" />
|
||||
</div>
|
||||
|
||||
<div v-show="!collapsedCategories[index]">
|
||||
<h5 class="text-xs font-semibold text-gray-600 dark:text-gray-400">Spørsmål & Svar</h5>
|
||||
|
||||
<div
|
||||
v-for="(qa, indexQA) in faq.categoryContent"
|
||||
:key="indexQA"
|
||||
class="flex items-start mt-2 gap-2 p-4 rounded border dark:border-gray-600 bg-gray-50 dark:bg-gray-800"
|
||||
draggable="true"
|
||||
@dragstart="handleDragStartQA($event, indexQA, index)"
|
||||
@dragover="handleDragOverQA"
|
||||
@drop="handleDropQA($event, indexQA, index)"
|
||||
>
|
||||
<!-- Drag handle -->
|
||||
<div class="cursor-move mt-1 text-gray-500 dark:text-gray-400">
|
||||
<Icon name="move" class="w-4 h-4" />
|
||||
</div>
|
||||
|
||||
<div class="flex-1 space-y-2">
|
||||
<div class="flex justify-between items-center">
|
||||
<span class="text-sm font-semibold text-gray-700 dark:text-gray-300">"{{ faq.categoryName }}" spørsmål {{ indexQA + 1 }}</span>
|
||||
<button @click="removeQA(indexQA, index)" class="text-red-500 hover:underline text-xs">Fjern</button>
|
||||
</div>
|
||||
|
||||
<div class="mb-2">
|
||||
<label class="block text-xs font-medium text-gray-600 dark:text-gray-400">Spørsmål</label>
|
||||
<input
|
||||
type="text"
|
||||
v-model="qa.question"
|
||||
class="block w-full rounded-md border-gray-300 dark:border-gray-600 shadow-sm focus:border-blue-500 focus:ring-blue-500 dark:bg-gray-800 dark:text-white"
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div class="mb-2">
|
||||
<label class="block text-xs font-medium text-gray-600 dark:text-gray-400">Svar</label>
|
||||
<textarea
|
||||
rows="3"
|
||||
v-model="qa.answer"
|
||||
class="block w-full rounded-md border-gray-300 dark:border-gray-600 shadow-sm focus:border-blue-500 focus:ring-blue-500 dark:bg-gray-800 dark:text-white"
|
||||
></textarea>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<button
|
||||
@click="addQA(index)"
|
||||
class="mt-2 px-3 py-1 bg-green-600 text-white text-xs rounded hover:bg-green-700"
|
||||
>
|
||||
Legg til spørsmål & svar
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<button @click="addFaqCategory" class="mt-2 px-4 py-2 bg-blue-600 text-white text-sm rounded hover:bg-blue-700">
|
||||
Legg til ny kategori
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
@@ -0,0 +1,117 @@
|
||||
<script setup>
|
||||
import { defineProps, ref, nextTick, onMounted } from 'vue';
|
||||
import { gsap } from 'gsap';
|
||||
import { initFlowbite } from 'flowbite';
|
||||
import { marked } from 'marked';
|
||||
|
||||
onMounted(() => {
|
||||
initFlowbite();
|
||||
faqs[0].categoryContent[0].state = true;
|
||||
});
|
||||
|
||||
const faqArticleContainer = ref(null);
|
||||
|
||||
const props = defineProps({
|
||||
block: {
|
||||
type: Object,
|
||||
required: true,
|
||||
},
|
||||
});
|
||||
|
||||
const faqs = props.block.faqs || [
|
||||
{
|
||||
categoryName: 'Kategori 1',
|
||||
categoryContent: [
|
||||
{ question: 'Spørsmål 1', answer: 'Dette er svar på spørsmål 1', state: false },
|
||||
{ question: 'Spørsmål 2', answer: 'Dette er svar på spørsmål 2', state: false },
|
||||
{ question: 'Spørsmål 3', answer: 'Dette er svar på spørsmål 3', state: false }
|
||||
]
|
||||
},
|
||||
{
|
||||
categoryName: 'Kategori 2',
|
||||
categoryContent: [
|
||||
{ question: 'Spørsmål 4', answer: 'Dette er svar på spørsmål 4', state: false },
|
||||
{ question: 'Spørsmål 5', answer: 'Dette er svar på spørsmål 5', state: false },
|
||||
{ question: 'Spørsmål 6', answer: 'Dette er svar på spørsmål 6', state: false }
|
||||
]
|
||||
}
|
||||
];
|
||||
|
||||
const setFalseExcept = (categoryIndex, qaIndex) => {
|
||||
faqs.forEach((category, cIdx) => {
|
||||
category.categoryContent.forEach((qa, qIdx) => {
|
||||
qa.state = cIdx === categoryIndex && qIdx === qaIndex;
|
||||
});
|
||||
});
|
||||
};
|
||||
|
||||
const showFaq = (categoryIndex, qaIndex) => {
|
||||
gsap.to(faqArticleContainer.value, {y: 200, opacity: 0, onComplete: () => {
|
||||
setFalseExcept(categoryIndex, qaIndex);
|
||||
// Wait for the DOM to update
|
||||
nextTick().then(() => {
|
||||
// Animate new articles in from the right
|
||||
gsap.fromTo(faqArticleContainer.value, {y: 200, opacity: 0}, {y: 0, opacity: 1});
|
||||
});
|
||||
}});
|
||||
};
|
||||
|
||||
const stateOf = (categoryIndex, qaIndex) => {
|
||||
return faqs[categoryIndex].categoryContent[qaIndex]?.state;
|
||||
};
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<section :id="block.anchorName" class="relative py-20 md:py-24 overflow-hidden mx-auto max-w-[1280px] bg-black">
|
||||
<div class="relative container px-4 mx-auto">
|
||||
<div class="max-w-7xl mx-auto">
|
||||
<div class="text-center mb-20">
|
||||
<span v-if="block.showlabel" class="inline-block py-1 px-3 mb-4 text-xs font-semibold text-orange-900 bg-orange-50 rounded-full">
|
||||
{{ block.label || 'Skriv inn en tekst for merkelappen' }}
|
||||
</span>
|
||||
<h1 v-if="block.showtitle" class="font-heading text-5xl xs:text-6xl md:text-7xl font-bold text-gray-500">
|
||||
<span>{{ block.title || 'Overskrift' }} </span>
|
||||
<span class="font-serif italic font-normal">{{ block.titlecursive || 'Overskriftkursiv' }}</span>
|
||||
</h1>
|
||||
</div>
|
||||
<div class="flex flex-wrap -mx-4 -mb-8">
|
||||
<div class="w-full lg:w-1/3 px-4 mb-15 lg:mb-0">
|
||||
<div class="flex flex-wrap -mx-2 lg:flex-col lg:max-w-sm border-b lg:border-b-0 lg:border-r border-gray-100">
|
||||
<div v-for="(faq, index) in faqs" :key="index" class="w-full md:w-1/2 lg:w-full px-2 mb-15">
|
||||
<h4 class="text-2xl font-semibold mb-6 text-amber-600">{{ faq.categoryName }}</h4>
|
||||
<ul class="mb-6">
|
||||
<li v-for="(category, catIndex) in faq.categoryContent" :key="catIndex" class="mb-6">
|
||||
<button type="button" @click="showFaq(index, catIndex)" class="flex items-center text-lg hover:text-amber-700" :class="stateOf(index, catIndex) ? 'text-amber-700 font-semibold' : 'text-white font-normal'">
|
||||
<svg width="12" height="12" viewbox="0 0 12 12" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<circle cx="6" cy="6" r="5" stroke="#C3C6CE" stroke-width="2"></circle>
|
||||
</svg>
|
||||
<span class="ml-3">
|
||||
{{ category.question }}
|
||||
</span>
|
||||
</button>
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="w-full lg:w-2/3 px-4" ref="faqArticleContainer">
|
||||
<template v-for="(faq, index) in faqs" :key="index">
|
||||
<template v-for="(category, catIndex) in faq.categoryContent" :key="catIndex">
|
||||
<div v-show="stateOf(index, catIndex)" class="max-w-xl xl:max-w-3xl mx-auto lg:mr-0">
|
||||
<h2 class="text-5xl font-semibold text-gray-500 mb-14">{{ category.question }}</h2>
|
||||
<div v-if="category.answer" v-html="marked(category.answer)" class="text-gray-300">
|
||||
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
</template>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
</template>
|
||||
|
||||
<style scoped>
|
||||
/* Optional FAQ list styles */
|
||||
</style>
|
||||
@@ -0,0 +1,33 @@
|
||||
<script setup>
|
||||
import { defineProps } from 'vue';
|
||||
|
||||
const props = defineProps({
|
||||
block: {
|
||||
type: Object,
|
||||
required: true,
|
||||
},
|
||||
});
|
||||
|
||||
const faqs = props.block.faqs || [
|
||||
{ question: 'Hva er dette?', answer: 'Dette er et eksempel på et spørsmål og svar.' },
|
||||
{ question: 'Hvordan fungerer det?', answer: 'Du legger til innhold og publiserer siden.' },
|
||||
];
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<section class="relative py-20 md:py-24 overflow-hidden mx-auto max-w-[1280px] bg-black">
|
||||
<div class="container mx-auto px-6 lg:px-20">
|
||||
<h2 class="text-amber-600 text-xl md:text-2xl font-serif mb-6">{{ block.title || 'Vanlige spørsmål' }}</h2>
|
||||
<ul class="space-y-4">
|
||||
<li v-for="(faq, index) in faqs" :key="index" class="border-b pb-4">
|
||||
<h3 class="text-lg font-semibold text-gray-200">{{ faq.question }}</h3>
|
||||
<p class="text-gray-300">{{ faq.answer }}</p>
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
</section>
|
||||
</template>
|
||||
|
||||
<style scoped>
|
||||
/* Optional FAQ list styles */
|
||||
</style>
|
||||
@@ -0,0 +1,99 @@
|
||||
<script setup>
|
||||
import Icon from '@/Components/Icon.vue'
|
||||
import { ref, toRef, watch } from 'vue';
|
||||
|
||||
const props = defineProps({
|
||||
block: { type: Object, required: true }
|
||||
});
|
||||
|
||||
const emit = defineEmits(['update:block']);
|
||||
|
||||
const local = toRef(props, 'block');
|
||||
|
||||
// Drag state
|
||||
const draggedIndex = ref(null);
|
||||
|
||||
function handleDragStart(event, index) {
|
||||
draggedIndex.value = index;
|
||||
event.dataTransfer.effectAllowed = 'move';
|
||||
event.dataTransfer.setData('text/plain', index);
|
||||
};
|
||||
|
||||
function handleDragOver(event) {
|
||||
event.preventDefault();
|
||||
event.dataTransfer.dropEffect = 'move';
|
||||
};
|
||||
|
||||
function handleDrop(event, index) {
|
||||
event.preventDefault();
|
||||
const fromIndex = draggedIndex.value;
|
||||
if (fromIndex === null || fromIndex === index) return;
|
||||
|
||||
const item = local.value.faqs.splice(fromIndex, 1)[0];
|
||||
local.value.faqs.splice(index, 0, item);
|
||||
draggedIndex.value = null;
|
||||
};
|
||||
|
||||
// End drag state
|
||||
|
||||
watch(local, (val) => {
|
||||
emit('update:block', val);
|
||||
}, { deep: true });
|
||||
|
||||
function addFaq() {
|
||||
local.value.faqs = local.value.faqs || [];
|
||||
local.value.faqs.push({ question: '', answer: '' });
|
||||
};
|
||||
|
||||
function removeFaq(index) {
|
||||
local.value.faqs.splice(index, 1);
|
||||
};
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div class="space-y-4">
|
||||
<div>
|
||||
<label class="block text-sm font-medium text-gray-700 dark:text-gray-300">Tittel for seksjonen</label>
|
||||
<input
|
||||
type="text"
|
||||
class="mt-1 block w-full rounded-md border-gray-300 dark:border-gray-600 shadow-sm focus:border-blue-500 focus:ring-blue-500 dark:bg-gray-800 dark:text-white"
|
||||
v-model="local.title"
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div class="space-y-4">
|
||||
<div
|
||||
v-for="(faq, index) in local.faqs"
|
||||
:key="index"
|
||||
class="flex items-start gap-2 p-4 rounded border dark:border-gray-600 bg-gray-50 dark:bg-gray-800"
|
||||
draggable="true"
|
||||
@dragstart="handleDragStart($event, index)"
|
||||
@dragover="handleDragOver"
|
||||
@drop="handleDrop($event, index)"
|
||||
>
|
||||
<!-- Drag handle -->
|
||||
<div class="cursor-move mt-1 text-gray-500 dark:text-gray-400">
|
||||
<Icon name="move" class="w-4 h-4" />
|
||||
</div>
|
||||
|
||||
<!-- Content -->
|
||||
<div class="flex-1 space-y-2">
|
||||
<div class="flex justify-between items-center">
|
||||
<span class="text-sm font-semibold text-gray-700 dark:text-gray-300">FAQ {{ index + 1 }}</span>
|
||||
<button @click="removeFaq(index)" class="text-red-500 hover:underline text-xs">Fjern</button>
|
||||
</div>
|
||||
|
||||
<div class="mb-2">
|
||||
<label class="block text-xs font-medium text-gray-600 dark:text-gray-400">Spørsmål</label>
|
||||
<input type="text" class="block w-full rounded-md border-gray-300 dark:border-gray-600 shadow-sm focus:border-blue-500 focus:ring-blue-500 dark:bg-gray-800 dark:text-white" v-model="faq.question" />
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<label class="block text-xs font-medium text-gray-600 dark:text-gray-400">Svar</label>
|
||||
<textarea rows="2" class="block w-full rounded-md border-gray-300 dark:border-gray-600 shadow-sm focus:border-blue-500 focus:ring-blue-500 dark:bg-gray-800 dark:text-white" v-model="faq.answer"></textarea>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
@@ -0,0 +1,33 @@
|
||||
<script setup>
|
||||
import { defineProps } from 'vue';
|
||||
|
||||
const props = defineProps({
|
||||
block: {
|
||||
type: Object,
|
||||
required: true,
|
||||
},
|
||||
});
|
||||
|
||||
const faqs = props.block.faqs || [
|
||||
{ question: 'Hva er dette?', answer: 'Dette er et eksempel på et spørsmål og svar.' },
|
||||
{ question: 'Hvordan fungerer det?', answer: 'Du legger til innhold og publiserer siden.' },
|
||||
];
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<section class="relative py-20 md:py-24 overflow-hidden mx-auto max-w-[1280px] bg-black">
|
||||
<div class="container mx-auto px-6 lg:px-20">
|
||||
<h2 class="text-amber-600 text-xl md:text-2xl font-serif mb-6">{{ block.title || 'Vanlige spørsmål' }}</h2>
|
||||
<ul class="space-y-4">
|
||||
<li v-for="(faq, index) in faqs" :key="index" class="border-b pb-4">
|
||||
<h3 class="text-lg font-semibold text-gray-200">{{ faq.question }}</h3>
|
||||
<p class="text-gray-300">{{ faq.answer }}</p>
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
</section>
|
||||
</template>
|
||||
|
||||
<style scoped>
|
||||
/* Optional FAQ list styles */
|
||||
</style>
|
||||
Reference in New Issue
Block a user