@dcodegroup-au/page-builder 0.1.8 → 0.2.0

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.
@@ -121,16 +121,29 @@ const page = {
121
121
  {
122
122
  title: "Logo cloud",
123
123
  type: "logo",
124
+ display: "horizontal",
124
125
  components: [
125
126
  {
126
127
  name: "Section header",
127
128
  type: "header",
128
- is_public: true,
129
+ supporting_text: "ELAA is proudly supporting",
129
130
  },
130
131
  {
131
132
  id: 2,
132
133
  name: "Logos",
133
134
  type: "logos",
135
+ max_items: 7,
136
+ height: 'medium',
137
+ data: [
138
+ {
139
+ url: "https://childroadsafety.org.au/",
140
+ logo: "https://beta-frontend.elaa.org.au/img/logos/starting_out_safely.png",
141
+ },
142
+ {
143
+ url: "https://carseatssavelives.com.au/",
144
+ logo: "https://beta-frontend.elaa.org.au/img/logos/car_seats_save_lives.jpg",
145
+ }
146
+ ]
134
147
  }
135
148
  ]
136
149
  },
@@ -213,6 +226,42 @@ const page = {
213
226
  }
214
227
  ]
215
228
  },
229
+ {
230
+ title: "Logo cloud",
231
+ type: "logo",
232
+ display: "vertical",
233
+ components: [
234
+ {
235
+ name: "Section header",
236
+ type: "header",
237
+ supporting_text: "ELAA’s preferred partners offer special benefits to ELAA members and support ELAA’s activities",
238
+ },
239
+ {
240
+ id: 2,
241
+ name: "Logos",
242
+ type: "logos",
243
+ max_items: 7,
244
+ height: 'small',
245
+ data: [
246
+ {
247
+ url: "/",
248
+ logo: "https://beta-frontend.elaa.org.au/img/logos/anzuk.png",
249
+ },
250
+ {
251
+ url: "/",
252
+ logo: "https://beta-frontend.elaa.org.au/img/logos/bunnings.png",
253
+ },
254
+ {
255
+ url: "/",
256
+ logo: "https://beta-frontend.elaa.org.au/img/logos/hesta.png",
257
+ },{
258
+ url: "/",
259
+ logo: "",
260
+ }
261
+ ]
262
+ }
263
+ ]
264
+ },
216
265
  {
217
266
  title: "Collection grid",
218
267
  type: "collection_grid",
@@ -0,0 +1,17 @@
1
+ <template>
2
+ <div class="text-lg pb-6 font-semibold text-gray-900 border-b border-gray-200">Instruction</div>
3
+ <div class="flex flex-col gap-3 py-6 text-gray-600">
4
+ <div class="font-semibold text-gray-900">Modules</div>
5
+ <p>Modules are fixed and for internal reference only, and admin users cannot make the following changes:</p>
6
+ <ul class="list-inside list-disc pl-3">
7
+ <li>Edit the title of any module (e.g., "Hero header").</li>
8
+ <li>Change the order of the modules.</li>
9
+ <li>Add new module.</li>
10
+ </ul>
11
+ <p>For any changes at the module level, please contact the development team.</p>
12
+ </div>
13
+ <div class="flex flex-col gap-3 py-6 text-gray-600">
14
+ <div class="font-semibold text-gray-900">Edit sub-modules or edit nested items</div>
15
+ <p>Click any sub-module to edit its settings and any nested items within it.</p>
16
+ </div>
17
+ </template>
@@ -2,9 +2,13 @@ import { createApp } from 'vue'
2
2
 
3
3
  import App from './App.vue'
4
4
  import { PageBuilder, ItemEdit, PageRender } from '../../dist/page-builder.es.js'
5
- import '../../dist/page-builder.css'
5
+ import '../../dist/page-builder.css';
6
+
7
+ // This is how to override upload component
8
+ // import VFileUpload from "./assets/ExampleComponent.vue";
6
9
 
7
10
  const app = createApp(App)
11
+ // app.provide("VFileUpload", VFileUpload)
8
12
  app.component('ItemEdit', ItemEdit)
9
13
  app.component('PageBuilder', PageBuilder)
10
14
  app.component('PageRender', PageRender)
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@dcodegroup-au/page-builder",
3
- "version": "0.1.8",
3
+ "version": "0.2.0",
4
4
  "exports": {
5
5
  ".": {
6
6
  "import": "./dist/page-builder.es.js"
@@ -99,8 +99,9 @@
99
99
  <VToggle v-model="form.public" name="public" label="Public" />
100
100
  </card>
101
101
  <card title="Featured Image" supporting_text="Recommended dimension: 1200*1060px">
102
- <FileUpload
102
+ <VFileUpload
103
103
  name="image"
104
+ height="h-[200px]"
104
105
  v-model="form.featured_image"
105
106
  />
106
107
  </card>
@@ -120,13 +121,16 @@
120
121
  </div>
121
122
  </template>
122
123
  <script setup>
123
- import { ref, computed } from "vue";
124
+ import { ref, computed, inject } from "vue";
124
125
  import QuillEditor from "@/components/common/QuillEditor.vue";
125
126
  import Card from "@/components/common/Card.vue";
126
127
  import LinkedTo from "@/components/common/LinkedTo.vue";
127
128
  import InputWrapper from "@/components/common/InputWrapper.vue";
128
129
  import VToggle from "@/components/common/VToggle.vue";
129
- import FileUpload from "@/components/common/FileUpload.vue";
130
+ import DefaultFileUpload from "@/components/common/FileUpload.vue";
131
+
132
+ // Inject the FileUpload component or fallback to the default one
133
+ const VFileUpload = inject("VFileUpload", DefaultFileUpload);
130
134
  import axios from "axios";
131
135
 
132
136
  const emit = defineEmits(["update"]);
@@ -70,6 +70,7 @@ import Instructions from "@/components/builders/Instructions.vue";
70
70
  import VItems from "@/components/builders/VItems.vue";
71
71
  import VLinks from "@/components/builders/VLinks.vue";
72
72
  import VHeader from "@/components/builders/VHeader.vue";
73
+ import VLogos from "@/components/builders/VLogos.vue";
73
74
 
74
75
  const emit = defineEmits(["save", "close"]);
75
76
  const props = defineProps({
@@ -94,6 +95,7 @@ const componentMaps = ref({
94
95
  links: markRaw(VLinks),
95
96
  header: markRaw(VHeader),
96
97
  link_grid: markRaw(VLinks),
98
+ logos: markRaw(VLogos),
97
99
  });
98
100
 
99
101
  if (!openStates.value) {
@@ -14,6 +14,7 @@ import {ref, markRaw} from "vue";
14
14
  import HeroHeader from "@/components/presenters/modules/HeroHeader.vue";
15
15
  import QuickLinks from "@/components/presenters/modules/QuickLinks.vue";
16
16
  import VTabs from "@/components/presenters/modules/VTabs.vue";
17
+ import LogoCloud from "@/components/presenters/modules/LogoCloud.vue";
17
18
 
18
19
  const props = defineProps({
19
20
  page: {
@@ -26,6 +27,7 @@ const componentMaps = ref({
26
27
  header: markRaw(HeroHeader),
27
28
  quick_links: markRaw(QuickLinks),
28
29
  tabs: markRaw(VTabs),
30
+ logo: markRaw(LogoCloud),
29
31
  });
30
32
 
31
33
  const currentComponent = (section) => {
@@ -4,6 +4,7 @@
4
4
  {{ dataRef.name }}
5
5
  </p>
6
6
  <input-wrapper
7
+ v-if="dataRef.hasOwnProperty('title')"
7
8
  is-vertical
8
9
  field="title"
9
10
  label-text="Title *"
@@ -22,18 +23,19 @@
22
23
  </input-wrapper>
23
24
  <input-wrapper
24
25
  is-vertical
26
+ v-if="dataRef.hasOwnProperty('supporting_text')"
25
27
  field="supporting_text"
26
28
  label-text="Supporting Text *"
27
29
  class="w-full mb-4"
28
30
  :value="dataRef.supporting_text"
29
- :limit="100"
31
+ :limit="dataRef.supporting_text_max_length ?? 100"
30
32
  >
31
33
  <input
32
34
  v-model="dataRef.supporting_text"
33
35
  name="supporting_text"
34
36
  type="text"
35
37
  placeholder="Supporting Text"
36
- :maxlength="100"
38
+ :maxlength="dataRef.supporting_text_max_length ?? 100"
37
39
  class="border-1 border-solid border-gray-300 rounded-lg bg-white w-full"
38
40
  />
39
41
  </input-wrapper>
@@ -0,0 +1,121 @@
1
+ <template>
2
+ <div class="flex justify-between pb-2">
3
+ <div class="flex justify-between w-full py-1">
4
+ <div>
5
+ <p class="text-lg font-semibold text-gray-900">
6
+ {{ componentData.name }}
7
+ </p>
8
+ <p class="text-sm text-gray-600 mt-1">
9
+ This section can contain up to {{ componentData.max_items }} logos
10
+ </p>
11
+ </div>
12
+ <div>
13
+ <a
14
+ @click="addItem"
15
+ 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"
16
+ :class="{ 'border-gray-100 bg-gray-100 !text-gray-400 hover:bg-gray-100': componentData.data?.length >= componentData.max_items }"
17
+ >
18
+ <PlusIcon class="h-5 w-5"></PlusIcon>
19
+ <span>Add</span>
20
+ </a>
21
+ </div>
22
+ </div>
23
+ </div>
24
+ <div class="flex flex-col gap-3">
25
+ <div
26
+ v-for="(item, index) in componentData.data"
27
+ class="flex flex-col gap-4 rounded-xl px-6 py-4 bg-gray-200"
28
+ :key="index"
29
+ :ref="index === componentData.data.length - 1 ? (el) => (lastItemRef = el) : null"
30
+ >
31
+ <div class="flex items-center justify-between">
32
+ <div class="text-lg font-semibold text-gray-900">
33
+ Logo #{{index + 1}}
34
+ </div>
35
+ <div class="relative flex items-end">
36
+ <ActionMenu @removeItem="handleDeleteItem(index)"/>
37
+ </div>
38
+ </div>
39
+ <div class="flex flex-col gap-6">
40
+ <div class="flex flex-col gap-1.5">
41
+ <VFileUpload
42
+ name="image"
43
+ background="bg-white"
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
+ <VModal ref="modalRef" entity="logo" :callback="deleteCallback"></VModal>
66
+ </template>
67
+ <script setup>
68
+ import {ref, nextTick, inject} from "vue";
69
+ import PlusIcon from "@/assets/img/icons/plus.svg";
70
+ import {defaultProps} from "@/components/helpers/defaultProps";
71
+ import ActionMenu from "@/components/common/ActionMenu.vue";
72
+ import InputWrapper from "@/components/common/InputWrapper.vue";
73
+ import VModal from "@/components/common/VModal.vue";
74
+ import DefaultFileUpload from "@/components/common/FileUpload.vue";
75
+
76
+ // Inject the FileUpload component or fallback to the default one
77
+ const VFileUpload = inject("VFileUpload", DefaultFileUpload);
78
+
79
+ const emit = defineEmits(["update"]);
80
+
81
+ const props = defineProps({
82
+ ...defaultProps,
83
+ });
84
+
85
+ const componentData = ref(props.data.component);
86
+ const modalRef = ref(null);
87
+ const lastItemRef = ref(null);
88
+ const deleteItemIndex = ref(null);
89
+
90
+ function addItem() {
91
+ if (!componentData.value.hasOwnProperty('data')) {
92
+ componentData.value.data = [];
93
+ }
94
+
95
+ if (componentData.value.data?.length >= componentData.value.max_items) {
96
+ return;
97
+ }
98
+ componentData.value.data?.push({
99
+ url: null,
100
+ logo: null,
101
+ });
102
+
103
+ nextTick(() => {
104
+ if (lastItemRef.value) {
105
+ lastItemRef.value.scrollIntoView({behavior: "smooth"});
106
+ }
107
+ });
108
+
109
+ emit("update", false);
110
+ }
111
+
112
+ const handleDeleteItem = (index) => {
113
+ deleteItemIndex.value = index;
114
+ modalRef?.value?.open(index);
115
+ }
116
+
117
+ const deleteCallback = (index) => {
118
+ componentData.value.data?.splice(index, 1);
119
+ emit("update", false);
120
+ };
121
+ </script>
@@ -5,7 +5,7 @@
5
5
  @todo handle upload here
6
6
  <span class="file-upload-preview">
7
7
  <img
8
- class="img rounded-lg"
8
+ class="img rounded-lg h-[200px] object-contain"
9
9
  :src="modelValue"
10
10
  title="Image"
11
11
  />
@@ -27,7 +27,7 @@
27
27
  <i class="fal fa-times" @click="deleteFile(file)"></i>
28
28
  </a>
29
29
  </div>
30
- <div class="relative" v-show="!modelValue">
30
+ <div :class="'relative rounded-lg ' + background" v-show="!modelValue">
31
31
  <div class="dropzone border border-dashed rounded-lg z-10 h-[200px] w-full cursor-pointer relative"
32
32
  ref="dropzone">
33
33
  </div>
@@ -79,6 +79,10 @@ const props = defineProps({
79
79
  type: String,
80
80
  required: false,
81
81
  },
82
+ background: {
83
+ type: String,
84
+ default: "",
85
+ }
82
86
  });
83
87
  const emit = defineEmits(["update:modelValue"]);
84
88
 
@@ -0,0 +1,49 @@
1
+ <template>
2
+ <div class="w-full relative" :class="{'bg-gray-50': !isHorizontal}">
3
+ <div class="1xl:max-w-[1824px] mx-auto relative flex justify-center items-center"
4
+ :class="{'py-8': isHorizontal, 'flex-col py-6': !isHorizontal}">
5
+ <div v-for="(component, index) in section.components">
6
+ <p v-if="component?.type === 'header' && component?.supporting_text"
7
+ class="mr-[48px] text-base text-gray-600 font-medium"
8
+ :class="{'mb-6': !isHorizontal}"
9
+ >
10
+ {{ component?.supporting_text }}
11
+ </p>
12
+ <div v-if="component?.type === 'logos'" class="flex flex-wrap justify-center" :class="{'gap-6': isHorizontal, 'gap-12': !isHorizontal}">
13
+ <template v-for="logo in component.data">
14
+ <a :href="logo?.url" title="Brand">
15
+ <img v-if="logo?.logo" :src="logo?.logo" :class="getLogoHeight(component?.height)" alt="Brand Logo"/>
16
+ <span v-else class="text-sm">No photo</span>
17
+ </a>
18
+ </template>
19
+ </div>
20
+ </div>
21
+ </div>
22
+ </div>
23
+ </template>
24
+
25
+ <script setup>
26
+ import {ref} from "vue";
27
+
28
+ const props = defineProps({
29
+ section: {
30
+ required: true,
31
+ type: Object,
32
+ },
33
+ });
34
+
35
+ const section = ref(props.section);
36
+ const isHorizontal = section.value.display === 'horizontal';
37
+
38
+ const getLogoHeight = (size) => {
39
+ if (size === 'small') {
40
+ return 'h-[32px]';
41
+ }
42
+
43
+ if (size === 'medium') {
44
+ return 'h-[40px]';
45
+ }
46
+
47
+ return 'h-[32px]';
48
+ };
49
+ </script>
@@ -1,29 +0,0 @@
1
- <template>
2
- <div class="flex flex-col gap-4">
3
- <div class="text-lg font-semibold text-gray-900">
4
- {{ props.data.item.name }}
5
- </div>
6
- <hr class="bg-gray-200" />
7
- <page-builder-base-module-form
8
- :form="props.data.item.form_component"
9
- :page="props.data.page"
10
- :data="dataRef"
11
- :attribute="props.data.pageAttribute"
12
- :module-index="props.data.subModuleIndex"
13
- :back-url="route('admin.pages.edit', { page: props.data.page.id })"
14
- :sites="sites"
15
- ></page-builder-base-module-form>
16
- </div>
17
- </template>
18
- <script setup>
19
- import { defaultProps } from "@/js/vue/components/admin/pages/common/defaultProps";
20
-
21
- const emit = defineEmits(["update"]);
22
- const route = inject("route");
23
-
24
- const props = defineProps({
25
- ...defaultProps,
26
- });
27
-
28
- const dataRef = ref(props.data.item);
29
- </script>