@dcodegroup-au/page-builder 0.2.8 → 0.2.9

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (69) hide show
  1. package/dist/page-builder.css +2 -2
  2. package/dist/page-builder.es.js +33372 -11858
  3. package/dist/page-builder.umd.js +59 -59
  4. package/example/src/App.vue +40 -679
  5. package/example/src/main.js +2 -1
  6. package/example/src/pages/BestLife.js +351 -0
  7. package/example/src/pages/Home.js +677 -0
  8. package/package.json +1 -1
  9. package/src/assets/icons.json +3569 -0
  10. package/src/components/ItemEdit.vue +147 -71
  11. package/src/components/LinkCardEdit.vue +139 -0
  12. package/src/components/PageBuilder.vue +43 -10
  13. package/src/components/PageRender.vue +12 -0
  14. package/src/components/builders/CollectionCarousel.vue +62 -0
  15. package/src/components/builders/Header.vue +82 -0
  16. package/src/components/builders/ImageBlock.vue +56 -0
  17. package/src/components/builders/Items.vue +112 -0
  18. package/src/components/builders/Links.vue +123 -0
  19. package/src/components/builders/Logos.vue +122 -0
  20. package/src/components/builders/NewsGrid.vue +59 -0
  21. package/src/components/builders/Paragraph.vue +93 -0
  22. package/src/components/builders/VideoGrid.vue +101 -0
  23. package/src/components/common/Button.vue +53 -0
  24. package/src/components/common/Card.vue +5 -1
  25. package/src/components/common/FileUpload.vue +1 -1
  26. package/src/components/common/Icon.vue +41 -0
  27. package/src/components/common/IconSelector.vue +106 -0
  28. package/src/components/common/LinkedTo.vue +9 -3
  29. package/src/components/helpers/bundleIcons.js +1189 -0
  30. package/src/components/index.js +2 -1
  31. package/src/components/presenters/components/{VCollectionGridPresenter.vue → CollectionGridPresenter.vue} +0 -5
  32. package/src/components/presenters/modules/CollectionCarousel.vue +2 -2
  33. package/src/components/presenters/modules/CollectionGrid.vue +2 -2
  34. package/src/components/presenters/modules/HeroHeader.vue +2 -2
  35. package/src/components/presenters/modules/LinkCard.vue +55 -0
  36. package/src/components/presenters/modules/LinkList.vue +51 -0
  37. package/src/components/presenters/modules/Paragraph.vue +26 -0
  38. package/src/components/presenters/modules/QuickLinks.vue +2 -2
  39. package/src/components/presenters/modules/StandardHeader.vue +32 -0
  40. package/src/components/presenters/modules/Timeline.vue +55 -0
  41. package/src/components/presenters/modules/TwoColumnsImageContent.vue +36 -0
  42. package/src/components/presenters/modules/VTabs.vue +2 -2
  43. package/src/utils/generateIconBundle.js +33 -0
  44. package/src/utils/generateIconJson.js +30 -0
  45. package/tailwind.config.js +5 -0
  46. package/src/components/builders/BaseModuleForm.vue +0 -86
  47. package/src/components/builders/LogoBuilder.vue +0 -167
  48. package/src/components/builders/PageBuilderCarousel.vue +0 -18
  49. package/src/components/builders/PageBuilderGrid.vue +0 -18
  50. package/src/components/builders/PageBuilderSectionHeader.vue +0 -30
  51. package/src/components/builders/PageModal.vue +0 -92
  52. package/src/components/builders/VCollectionCarousel.vue +0 -58
  53. package/src/components/builders/VHeader.vue +0 -55
  54. package/src/components/builders/VItems.vue +0 -110
  55. package/src/components/builders/VLinks.vue +0 -121
  56. package/src/components/builders/VLogos.vue +0 -120
  57. package/src/components/builders/VNewsGrid.vue +0 -55
  58. package/src/components/builders/VVideoGrid.vue +0 -99
  59. package/src/components/common/forms/LogosForm.vue +0 -39
  60. package/src/components/common/forms/PageBuilderLinksForm.vue +0 -39
  61. package/src/components/common/forms/SectionHeaderForm.vue +0 -45
  62. package/src/components/common/forms/TabForm.vue +0 -90
  63. /package/src/components/common/{VModal.vue → Modal.vue} +0 -0
  64. /package/src/components/common/{VToggle.vue → Toggle.vue} +0 -0
  65. /package/src/components/presenters/components/{VCarouselPresenter.vue → CarouselPresenter.vue} +0 -0
  66. /package/src/components/presenters/components/{VHeaderPresenter.vue → HeaderPresenter.vue} +0 -0
  67. /package/src/components/presenters/components/{VLinkPresenter.vue → LinkPresenter.vue} +0 -0
  68. /package/src/components/presenters/components/{VSliderPresenter.vue → SliderPresenter.vue} +0 -0
  69. /package/src/components/presenters/components/{VVerticalTabPresenter.vue → VerticalTabPresenter.vue} +0 -0
@@ -0,0 +1,82 @@
1
+ <template>
2
+ <div class="bg-gray-50 px-6 py-5 rounded-xl">
3
+ <p class="text-lg font-semibold text-gray-900 border-b border-gray-200 pb-4">
4
+ {{ dataRef.name }}
5
+ </p>
6
+ <div class="flex flex-col gap-4 rounded-xl py-5" :class="{'bg-gray-50 px-0': dataRef?.has_extra, 'bg-gray-200 px-6': !dataRef?.hasOwnProperty('has_extra')}">
7
+ <input-wrapper
8
+ v-if="dataRef.hasOwnProperty('title')"
9
+ is-vertical
10
+ field="title"
11
+ label-text="Title *"
12
+ class="w-full mb-4"
13
+ :value="dataRef.title"
14
+ :limit="51"
15
+ >
16
+ <input
17
+ v-model="dataRef.title"
18
+ name="title"
19
+ type="text"
20
+ placeholder="Title"
21
+ :maxlength="51"
22
+ class="border-1 border-solid border-gray-300 rounded-lg bg-white w-full"
23
+ />
24
+ </input-wrapper>
25
+ <input-wrapper
26
+ is-vertical
27
+ v-if="dataRef.hasOwnProperty('supporting_text')"
28
+ field="supporting_text"
29
+ label-text="Supporting Text *"
30
+ class="w-full mb-4"
31
+ :value="dataRef.supporting_text"
32
+ :limit="dataRef.supporting_text_max_length ?? 100"
33
+ >
34
+ <input
35
+ v-model="dataRef.supporting_text"
36
+ name="supporting_text"
37
+ type="text"
38
+ placeholder="Supporting Text"
39
+ :maxlength="dataRef.supporting_text_max_length ?? 100"
40
+ class="border-1 border-solid border-gray-300 rounded-lg bg-white w-full"
41
+ />
42
+ </input-wrapper>
43
+ </div>
44
+ <VButton v-if="dataRef.hasOwnProperty('primary_button')" :button="dataRef.primary_button" :sites="sites" :classes="'bg-gray-200'"/>
45
+ <VButton v-if="dataRef.hasOwnProperty('secondary_button')" :button="dataRef.secondary_button" :sites="sites" :classes="'bg-gray-200'"/>
46
+ <div class="flex flex-col gap-4 bg-gray-200 rounded-xl px-6 py-5 mt-4"
47
+ v-if="dataRef.hasOwnProperty('featured_image')">
48
+ <card class="!p-0 !bg-gray-200" title="Featured Image"
49
+ supporting_text="Recommended dimension: 1200*1060px">
50
+ <VFileUpload
51
+ name="image"
52
+ class="!bg-gray-200"
53
+ height="h-[395px]"
54
+ v-model="dataRef.featured_image"
55
+ />
56
+ </card>
57
+ </div>
58
+ </div>
59
+ </template>
60
+ <script setup>
61
+ import {ref, inject} from "vue";
62
+ import {defaultProps} from "@/components/helpers/defaultProps";
63
+ import Card from "@/components/common/Card.vue";
64
+ import InputWrapper from "@/components/common/InputWrapper.vue";
65
+ import DefaultFileUpload from "@/components/common/FileUpload.vue";
66
+ import VButton from "@/components/common/Button.vue";
67
+
68
+ // Inject the FileUpload component or fallback to the default one
69
+ const VFileUpload = inject("VFileUpload", DefaultFileUpload);
70
+
71
+ const emit = defineEmits(["update"]);
72
+ const props = defineProps({
73
+ ...defaultProps,
74
+ hasExtra: {
75
+ type: Boolean,
76
+ default: true
77
+ },
78
+ });
79
+
80
+ const dataRef = ref(props.data.component);
81
+ console.log(dataRef)
82
+ </script>
@@ -0,0 +1,56 @@
1
+ <template>
2
+ <div class="rounded-xl bg-gray-50 px-6 py-5">
3
+ <p class="text-lg font-semibold text-gray-900 pb-4">
4
+ {{ dataRef.name }}
5
+ </p>
6
+ <div class="rounded-xl bg-gray-200" v-if="dataRef.hasOwnProperty('featured_image')">
7
+ <card :classes="'bg-gray-200'" title="Featured Image"
8
+ supporting_text="Recommended dimension: 1200*1060px">
9
+ <VFileUpload
10
+ name="image"
11
+ height="h-[395px]"
12
+ v-model="dataRef.featured_image"
13
+ />
14
+ </card>
15
+ </div>
16
+ <div class="flex flex-col gap-4 rounded-xl bg-gray-200 px-6 py-5 mt-4" v-if="dataRef.hasOwnProperty('caption')">
17
+ <p class="text-lg font-semibold text-gray-900 border-b border-gray-200">
18
+ Caption
19
+ </p>
20
+ <input-wrapper
21
+ is-vertical
22
+ field="caption"
23
+ label-text="Caption *"
24
+ class="w-full mb-4"
25
+ :value="captionWordCount"
26
+ :show-count="true"
27
+ >
28
+ <QuillEditor v-model="dataRef.caption"/>
29
+ </input-wrapper>
30
+ </div>
31
+ </div>
32
+ </template>
33
+ <script setup>
34
+ import {ref, computed, inject} from "vue";
35
+ import {defaultProps} from "@/components/helpers/defaultProps";
36
+ import InputWrapper from "@/components/common/InputWrapper.vue";
37
+ import QuillEditor from "@/components/common/QuillEditor.vue";
38
+ import Card from "@/components/common/Card.vue";
39
+ import DefaultFileUpload from "@/components/common/FileUpload.vue";
40
+
41
+ // Inject the FileUpload component or fallback to the default one
42
+ const VFileUpload = inject("VFileUpload", DefaultFileUpload);
43
+
44
+ const emit = defineEmits(["update"]);
45
+ const props = defineProps({
46
+ ...defaultProps,
47
+ });
48
+
49
+ const dataRef = ref(props.data.component);
50
+
51
+ const captionWordCount = computed(() => {
52
+ const plainText = dataRef.value.caption?.replace(/<[^>]*>/g, ' ').trim();
53
+ const words = plainText.split(/\s+/).filter(word => word.length > 0);
54
+ return words.length;
55
+ });
56
+ </script>
@@ -0,0 +1,112 @@
1
+ <template>
2
+ <div class="rounded-xl bg-gray-50 px-6 py-5">
3
+ <div class="flex justify-between pb-2">
4
+ <p class="text-lg font-semibold text-gray-900">
5
+ {{ dataRef.name }}
6
+ </p>
7
+ </div>
8
+ <div class="flex flex-col gap-4">
9
+ <div class="text-gray-600 border-b border-gray-200 pb-4 text-sm">
10
+ {{ dataRef.supportive_text }}
11
+ </div>
12
+ <div class="flex flex-col gap-3">
13
+ <div class="flex justify-between">
14
+ <div class="flex flex-col gap-1">
15
+ <div class="font-semibold text-gray-900">{{ parseName(type) }}</div>
16
+ <div class="text-sm text-gray-600">This {{ singularize(type) }} can contain up to {{ dataRef.max_items }} {{ parseName(type, false) }}</div>
17
+ </div>
18
+ <div>
19
+ <button
20
+ :disabled="dataRef.data.length >= dataRef.max_items"
21
+ @click="addItem"
22
+ type="button"
23
+ class="text-sm cursor-pointer flex items-center justify-center gap-1 rounded-[99px] border border-brand-600 bg-brand-500 px-3.5 py-2 font-semibold text-white hover:bg-brand-600"
24
+ :class="{ 'border-gray-100 bg-gray-100 !text-gray-400 hover:bg-gray-100': dataRef.data.length >= dataRef.max_items }"
25
+ >
26
+ <PlusIcon class="h-5 w-5"></PlusIcon>
27
+ <span>Add</span>
28
+ </button>
29
+ </div>
30
+ </div>
31
+ <div class="flex flex-col gap-3">
32
+ <div v-for="(item, index) in dataRef.data"
33
+ class="flex items-center gap-4 px-2 py-1 hover:bg-gray-100 rounded-lg"
34
+ :class="{'bg-gray-200 hover:bg-gray-200': openItemStates[index]}">
35
+ <div class="flex flex-1 items-center justify-between relative" @click="toggleItem(index);">
36
+ <div class="flex flex-1 flex-col cursor-pointer" @click="edit(item, index)">
37
+ <div class="text-xs text-gray-600">
38
+ {{ singularize(parseName(type)) }} #{{ index + 1 }}
39
+ </div>
40
+ <div class="text-sm font-medium text-gray-900">
41
+ {{ item.title }}
42
+ </div>
43
+ </div>
44
+ <ActionMenu @removeItem="handleDeleteItem(index)" :enable-edit="true" @editItem="edit(item, index)"/>
45
+ </div>
46
+ </div>
47
+ </div>
48
+ </div>
49
+ </div>
50
+ </div>
51
+ <VModal ref="modalRef" :entity="singularize(type)" :callback="deleteCallback"></VModal>
52
+ </template>
53
+ <script setup>
54
+ import {ref} from "vue";
55
+ import PlusIcon from "@/assets/img/icons/plus.svg";
56
+ import { defaultProps } from "@/components/helpers/defaultProps";
57
+ import { singularize, parseName } from "@/components/helpers/common";
58
+ import { createItem } from "@/components/helpers/pageBuilderFactory";
59
+ import ActionMenu from "@/components/common/ActionMenu.vue";
60
+ import VModal from "@/components/common/Modal.vue";
61
+
62
+ const emit = defineEmits(["update"]);
63
+ const props = defineProps({
64
+ ...defaultProps,
65
+ });
66
+
67
+ const dataRef = ref(props.data.component);
68
+ const type = dataRef.value.type;
69
+ const modalRef = ref(null);
70
+
71
+ const key = `${type.value}-openItemStates`;
72
+ const openItemStates = ref(JSON.parse(window.localStorage.getItem(key)));
73
+ if (!openItemStates.value) {
74
+ openItemStates.value = {};
75
+
76
+ dataRef.value.data.forEach((item, index) => {
77
+ openItemStates.value[index] = false;
78
+ });
79
+
80
+ window.localStorage.setItem(key, JSON.stringify(openItemStates.value));
81
+ }
82
+
83
+ const toggleItem = (index) => {
84
+ Object.keys(openItemStates.value).forEach((key) => {
85
+ openItemStates.value[key] = false;
86
+ });
87
+ openItemStates.value[index] = !openItemStates.value[index];
88
+ window.localStorage.setItem(key, JSON.stringify(openItemStates.value));
89
+ };
90
+
91
+ const addItem = () => {
92
+ dataRef.value.data.push(createItem({}, type === 'sliders'));
93
+ emit("update", false);
94
+ };
95
+
96
+ const handleDeleteItem = (index) => {
97
+ modalRef?.value?.open(index);
98
+ };
99
+
100
+ const deleteCallback = (index) => {
101
+ dataRef.value.data.splice(index, 1);
102
+ emit("update", false);
103
+ };
104
+
105
+ const edit = (item, index) => {
106
+ if (item.hasOwnProperty('edit_url')) {
107
+ window.location.href = item.edit_url;
108
+ }
109
+
110
+ window.location.href = `/admin/pages/${props?.data?.page?.id}/sections/${props?.data?.sectionIndex}/components/${props?.data?.componentIndex}/items/${index}`;
111
+ };
112
+ </script>
@@ -0,0 +1,123 @@
1
+ <template>
2
+ <div class="rounded-xl bg-gray-50 px-6 py-5">
3
+ <div class="flex justify-between pb-2">
4
+ <div class="flex justify-between w-full py-1">
5
+ <div>
6
+ <p class="text-lg font-semibold text-gray-900">
7
+ {{ componentData.name }}
8
+ </p>
9
+ <p class="text-sm text-gray-600 mt-1">
10
+ {{ componentData.supportive_text }}
11
+ </p>
12
+ </div>
13
+ <div>
14
+ <a
15
+ @click="addItem"
16
+ class="text-sm cursor-pointer flex items-center justify-center gap-1 rounded-[99px] border border-brand-600 bg-brand-500 px-3.5 py-2 font-semibold text-white hover:bg-brand-600"
17
+ :class="{ 'border-gray-100 bg-gray-100 !text-gray-400 hover:bg-gray-100': componentData.data?.length >= componentData.max_items }"
18
+ >
19
+ <PlusIcon class="h-5 w-5"></PlusIcon>
20
+ <span>Add</span>
21
+ </a>
22
+ </div>
23
+ </div>
24
+ </div>
25
+ <div class="flex flex-col gap-3">
26
+ <div
27
+ v-for="(item, index) in componentData.data"
28
+ class="flex flex-col gap-4 rounded-xl px-6 py-4 bg-gray-200"
29
+ :key="index"
30
+ :ref="index === componentData.data.length - 1 ? (el) => (lastItemRef = el) : null"
31
+ >
32
+ <div class="flex items-center justify-between">
33
+ <div class="text-lg font-semibold text-gray-900">
34
+ Link #{{ index + 1 }}
35
+ </div>
36
+ <div class="relative flex items-end">
37
+ <ActionMenu @removeItem="handleDeleteItem(index)"/>
38
+ </div>
39
+ </div>
40
+ <div class="flex flex-col gap-6">
41
+ <div class="flex flex-col gap-1.5">
42
+ <input-wrapper
43
+ is-vertical
44
+ field="title"
45
+ label-text="Title *"
46
+ class="w-full mb-4"
47
+ :value="item.title"
48
+ :limit="20"
49
+ >
50
+ <input
51
+ v-model="item.title"
52
+ name="title"
53
+ type="text"
54
+ placeholder="Title"
55
+ :maxlength="20"
56
+ class="border-1 border-solid border-gray-300 rounded-lg bg-white w-full"
57
+ />
58
+ </input-wrapper>
59
+ <linked-to
60
+ label="Linked to"
61
+ :name="`link_${index + 1}`"
62
+ v-model:type="item.type"
63
+ v-model:url="item.url"
64
+ v-model:openInNewTab="item.open_in_new_tab"
65
+ :sites="sites"
66
+ />
67
+ </div>
68
+ </div>
69
+ </div>
70
+ </div>
71
+ </div>
72
+ <VModal ref="modalRef" entity="link" :callback="deleteCallback"></VModal>
73
+ </template>
74
+ <script setup>
75
+ import {ref, nextTick} from "vue";
76
+ import PlusIcon from "@/assets/img/icons/plus.svg";
77
+ import {defaultProps} from "@/components/helpers/defaultProps";
78
+ import {createLink} from "@/components/helpers/pageBuilderFactory";
79
+ import ActionMenu from "@/components/common/ActionMenu.vue";
80
+ import InputWrapper from "@/components/common/InputWrapper.vue";
81
+ import LinkedTo from "@/components/common/LinkedTo.vue";
82
+ import VModal from "@/components/common/Modal.vue";
83
+
84
+ const emit = defineEmits(["update"]);
85
+
86
+ const props = defineProps({
87
+ ...defaultProps,
88
+ });
89
+
90
+ const componentData = ref(props.data.component);
91
+ const modalRef = ref(null);
92
+ const lastItemRef = ref(null);
93
+ const deleteItemIndex = ref(null);
94
+
95
+ function addItem() {
96
+ if (!componentData.value.hasOwnProperty('data')) {
97
+ componentData.value.data = [];
98
+ }
99
+
100
+ if (componentData.value.data?.length >= componentData.value.max_items) {
101
+ return;
102
+ }
103
+ componentData.value.data?.push(createLink());
104
+
105
+ nextTick(() => {
106
+ if (lastItemRef.value) {
107
+ lastItemRef.value.scrollIntoView({behavior: "smooth"});
108
+ }
109
+ });
110
+
111
+ emit("update", false);
112
+ }
113
+
114
+ const handleDeleteItem = (index) => {
115
+ deleteItemIndex.value = index;
116
+ modalRef?.value?.open(index);
117
+ }
118
+
119
+ const deleteCallback = (index) => {
120
+ componentData.value.data?.splice(index, 1);
121
+ emit("update", false);
122
+ };
123
+ </script>
@@ -0,0 +1,122 @@
1
+ <template>
2
+ <div class="rounded-xl bg-gray-50 px-6 py-5">
3
+ <div class="flex justify-between pb-2">
4
+ <div class="flex justify-between w-full py-1">
5
+ <div>
6
+ <p class="text-lg font-semibold text-gray-900">
7
+ {{ componentData.name }}
8
+ </p>
9
+ <p class="text-sm text-gray-600 mt-1">
10
+ This section can contain up to {{ componentData.max_items }} logos
11
+ </p>
12
+ </div>
13
+ <div>
14
+ <a
15
+ @click="addItem"
16
+ class="text-sm cursor-pointer flex items-center justify-center gap-1 rounded-[99px] border border-brand-600 bg-brand-500 px-3.5 py-2 font-semibold text-white hover:bg-brand-600"
17
+ :class="{ 'border-gray-100 bg-gray-100 !text-gray-400 hover:bg-gray-100': componentData.data?.length >= componentData.max_items }"
18
+ >
19
+ <PlusIcon class="h-5 w-5"></PlusIcon>
20
+ <span>Add</span>
21
+ </a>
22
+ </div>
23
+ </div>
24
+ </div>
25
+ <div class="flex flex-col gap-3">
26
+ <div
27
+ v-for="(item, index) in componentData.data"
28
+ class="flex flex-col gap-4 rounded-xl px-6 py-4 bg-gray-200"
29
+ :key="index"
30
+ :ref="index === componentData.data.length - 1 ? (el) => (lastItemRef = el) : null"
31
+ >
32
+ <div class="flex items-center justify-between">
33
+ <div class="text-lg font-semibold text-gray-900">
34
+ Logo #{{ index + 1 }}
35
+ </div>
36
+ <div class="relative flex items-end">
37
+ <ActionMenu @removeItem="handleDeleteItem(index)"/>
38
+ </div>
39
+ </div>
40
+ <div class="flex flex-col gap-6">
41
+ <div class="flex flex-col gap-1.5">
42
+ <VFileUpload
43
+ name="image"
44
+ v-model="item.logo"
45
+ />
46
+ <input-wrapper
47
+ is-vertical
48
+ field="url"
49
+ label-text="Url *"
50
+ class="w-full my-4"
51
+ :value="item.url"
52
+ >
53
+ <input
54
+ v-model="item.url"
55
+ name="url"
56
+ type="text"
57
+ placeholder="Enter your url"
58
+ class="border-1 border-solid border-gray-300 rounded-lg bg-white w-full"
59
+ />
60
+ </input-wrapper>
61
+ </div>
62
+ </div>
63
+ </div>
64
+ </div>
65
+ </div>
66
+ <VModal ref="modalRef" entity="logo" :callback="deleteCallback"></VModal>
67
+ </template>
68
+ <script setup>
69
+ import {ref, nextTick, inject} from "vue";
70
+ import PlusIcon from "@/assets/img/icons/plus.svg";
71
+ import {defaultProps} from "@/components/helpers/defaultProps";
72
+ import ActionMenu from "@/components/common/ActionMenu.vue";
73
+ import InputWrapper from "@/components/common/InputWrapper.vue";
74
+ import VModal from "@/components/common/Modal.vue";
75
+ import DefaultFileUpload from "@/components/common/FileUpload.vue";
76
+
77
+ // Inject the FileUpload component or fallback to the default one
78
+ const VFileUpload = inject("VFileUpload", DefaultFileUpload);
79
+
80
+ const emit = defineEmits(["update"]);
81
+
82
+ const props = defineProps({
83
+ ...defaultProps,
84
+ });
85
+
86
+ const componentData = ref(props.data.component);
87
+ const modalRef = ref(null);
88
+ const lastItemRef = ref(null);
89
+ const deleteItemIndex = ref(null);
90
+
91
+ function addItem() {
92
+ if (!componentData.value.hasOwnProperty('data')) {
93
+ componentData.value.data = [];
94
+ }
95
+
96
+ if (componentData.value.data?.length >= componentData.value.max_items) {
97
+ return;
98
+ }
99
+ componentData.value.data?.push({
100
+ url: null,
101
+ logo: null,
102
+ });
103
+
104
+ nextTick(() => {
105
+ if (lastItemRef.value) {
106
+ lastItemRef.value.scrollIntoView({behavior: "smooth"});
107
+ }
108
+ });
109
+
110
+ emit("update", false);
111
+ }
112
+
113
+ const handleDeleteItem = (index) => {
114
+ deleteItemIndex.value = index;
115
+ modalRef?.value?.open(index);
116
+ }
117
+
118
+ const deleteCallback = (index) => {
119
+ componentData.value.data?.splice(index, 1);
120
+ emit("update", false);
121
+ };
122
+ </script>
@@ -0,0 +1,59 @@
1
+ <template>
2
+ <div class="rounded-xl bg-gray-50 px-6 py-5">
3
+ <div class="flex flex-col">
4
+ <div class="flex flex-col gap-4 mb-4 border-b border-gray-200 pb-4">
5
+ <p class="text-lg font-semibold text-gray-900 border-b border-gray-200 pb-4">
6
+ Grid
7
+ </p>
8
+ <VToggle name="show" v-model="componentData.button.show" title="Show Button"/>
9
+ <input-wrapper
10
+ is-vertical
11
+ field="title"
12
+ label-text="Title *"
13
+ class="w-full mb-4"
14
+ :value="componentData.button.title"
15
+ :limit="20"
16
+ >
17
+ <input
18
+ v-model="componentData.button.title"
19
+ name="title"
20
+ type="text"
21
+ placeholder="Title"
22
+ :maxlength="20"
23
+ class="border-1 border-solid border-gray-300 rounded-lg bg-white w-full"
24
+ />
25
+ </input-wrapper>
26
+ <linked-to
27
+ v-if="componentData?.button"
28
+ label="Link to"
29
+ name="button"
30
+ v-model:type="componentData.button.type"
31
+ v-model:url="componentData.button.url"
32
+ v-model:openInNewTab="componentData.button.open_in_new_tab"
33
+ :sites="sites"
34
+ />
35
+ </div>
36
+ <div>
37
+ <h3 v-if="componentData.content?.label" class="text-base text-gray-900 font-semibold">
38
+ {{ componentData.content.label }}</h3>
39
+ <p v-if="componentData.content?.supportive_text" class="text-gray-600 text-base font-normal mt-2">
40
+ {{ componentData.content.supportive_text }}</p>
41
+ </div>
42
+ </div>
43
+ </div>
44
+ </template>
45
+ <script setup>
46
+ import {ref} from "vue";
47
+ import {defaultProps} from "@/components/helpers/defaultProps";
48
+ import LinkedTo from "@/components/common/LinkedTo.vue";
49
+ import InputWrapper from "@/components/common/InputWrapper.vue";
50
+ import VToggle from "@/components/common/Toggle.vue";
51
+
52
+ const emit = defineEmits(["update"]);
53
+
54
+ const props = defineProps({
55
+ ...defaultProps,
56
+ });
57
+
58
+ const componentData = ref(props.data.component);
59
+ </script>
@@ -0,0 +1,93 @@
1
+ <template>
2
+ <div class="rounded-xl bg-gray-50 px-6 py-5">
3
+ <p class="text-lg font-semibold text-gray-900 border-b border-gray-200 pb-4">
4
+ {{ dataRef.name }}
5
+ </p>
6
+ <div class="flex flex-col gap-4 rounded-xl bg-gray-200 px-6 py-5">
7
+ <p class="text-lg font-semibold text-gray-900">
8
+ Description
9
+ </p>
10
+ <input-wrapper
11
+ v-if="dataRef.hasOwnProperty('eyebrow_heading')"
12
+ is-vertical
13
+ field="eyebrow_heading"
14
+ label-text="Eyebrow heading *"
15
+ class="w-full mb-4"
16
+ :value="dataRef.eyebrow_heading"
17
+ :limit="51"
18
+ >
19
+ <input
20
+ v-model="dataRef.eyebrow_heading"
21
+ name="eyebrow_heading"
22
+ type="text"
23
+ placeholder="Eyebrow heading"
24
+ :maxlength="51"
25
+ class="border-1 border-solid border-gray-300 rounded-lg bg-white w-full"
26
+ />
27
+ </input-wrapper>
28
+ <input-wrapper
29
+ v-if="dataRef.hasOwnProperty('title')"
30
+ is-vertical
31
+ field="title"
32
+ label-text="Title *"
33
+ class="w-full mb-4"
34
+ :value="dataRef.title"
35
+ :limit="51"
36
+ >
37
+ <input
38
+ v-model="dataRef.title"
39
+ name="title"
40
+ type="text"
41
+ placeholder="Title"
42
+ :maxlength="51"
43
+ class="border-1 border-solid border-gray-300 rounded-lg bg-white w-full"
44
+ />
45
+ </input-wrapper>
46
+ <input-wrapper
47
+ is-vertical
48
+ field="description"
49
+ label-text="Description *"
50
+ class="w-full mb-4"
51
+ :value="paragraphWordCount"
52
+ :show-count="true"
53
+ >
54
+ <QuillEditor v-model="dataRef.paragraph"/>
55
+ </input-wrapper>
56
+
57
+ </div>
58
+ <div class="rounded-xl bg-gray-200 mt-4" v-if="dataRef.hasOwnProperty('featured_image')">
59
+ <card :classes="'bg-gray-200'" title="Featured Image"
60
+ supporting_text="Recommended dimension: 1200*1060px">
61
+ <VFileUpload
62
+ name="image"
63
+ height="h-[395px]"
64
+ v-model="dataRef.featured_image"
65
+ />
66
+ </card>
67
+ </div>
68
+ </div>
69
+ </template>
70
+ <script setup>
71
+ import {ref, computed, inject} from "vue";
72
+ import {defaultProps} from "@/components/helpers/defaultProps";
73
+ import InputWrapper from "@/components/common/InputWrapper.vue";
74
+ import QuillEditor from "@/components/common/QuillEditor.vue";
75
+ import Card from "@/components/common/Card.vue";
76
+ import DefaultFileUpload from "@/components/common/FileUpload.vue";
77
+
78
+ // Inject the FileUpload component or fallback to the default one
79
+ const VFileUpload = inject("VFileUpload", DefaultFileUpload);
80
+
81
+ const emit = defineEmits(["update"]);
82
+ const props = defineProps({
83
+ ...defaultProps,
84
+ });
85
+
86
+ const dataRef = ref(props.data.component);
87
+
88
+ const paragraphWordCount = computed(() => {
89
+ const plainText = dataRef.value.paragraph?.replace(/<[^>]*>/g, ' ').trim();
90
+ const words = plainText.split(/\s+/).filter(word => word.length > 0);
91
+ return words.length;
92
+ });
93
+ </script>