@dcodegroup-au/page-builder 0.2.5 → 0.2.6

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.
@@ -269,12 +269,73 @@ const page = {
269
269
  {
270
270
  name: "Section header",
271
271
  type: "header",
272
- title: 'Services',
273
- supporting_text: 'Our knowledge and expertise of the early childhood sector enables ELAA to provide expert professional advice and support.',
272
+ title: 'News',
273
+ dark: true,
274
+ center: true,
275
+ supporting_text: 'Stay Informed with the Latest Developments in Early Education',
274
276
  },
275
277
  {
276
278
  name: "Grid",
277
- type: "grid",
279
+ type: "news_grid",
280
+ button: {
281
+ title: 'View all news',
282
+ url: 'google.com', // external could be an url
283
+ type: 'external-page',
284
+ is_new_tab: true,
285
+ show: true,
286
+ },
287
+ content: {
288
+ label: 'Content',
289
+ supportive_text: 'This grid will be automatically populated with the latest three news items.',
290
+ items: [
291
+ {
292
+ image: "https://beta-frontend.elaa.org.au/img/news/news_1.jpg",
293
+ categories: [{
294
+ name: 'Wages, IR and Governance',
295
+ style: 'navy',
296
+ }],
297
+ link: "https://example.com",
298
+ title: "Fair Work Laws changes for casual employment",
299
+ dateTime: "10 Sep 2024",
300
+ },
301
+ {
302
+ image: "https://beta-frontend.elaa.org.au/img/news/news_2.jpg",
303
+ categories: [{
304
+ name: 'Member Benefits',
305
+ style: 'brand',
306
+ }],
307
+ link: "https://example.com",
308
+ title: "ELAA renews its partnership with Bunnings for another year: make the most of your Bunnings PowerPass membership benefits",
309
+ dateTime: "09 Sep 2024",
310
+ },
311
+ {
312
+ image: "https://beta-frontend.elaa.org.au/img/news/news_3.jpg",
313
+ categories: [{
314
+ name: 'Advocacy',
315
+ style: 'orange',
316
+ }],
317
+ link: "https://example.com",
318
+ title: "Meet our Best Start Best Life (BSBL) Reform and Engagement Advisor Leanne Rodriguez",
319
+ dateTime: "08 Sep 2024",
320
+ },
321
+ ]
322
+ }
323
+ },
324
+ {
325
+ name: "Grid",
326
+ type: "video_grid",
327
+ max_items: 3,
328
+ data: [
329
+ {
330
+ video: "http://commondatastorage.googleapis.com/gtv-videos-bucket/sample/ForBiggerFun.mp4",
331
+ },
332
+ {
333
+ video: "http://commondatastorage.googleapis.com/gtv-videos-bucket/sample/ElephantsDream.mp4",
334
+ },
335
+ {
336
+ video: "http://commondatastorage.googleapis.com/gtv-videos-bucket/sample/ForBiggerEscapes.mp4",
337
+ }
338
+ ]
278
339
  }
279
340
  ]
280
341
  },
@@ -432,7 +493,7 @@ const page = {
432
493
  },
433
494
  ]
434
495
  }
435
- }
496
+ },
436
497
  ]
437
498
  },
438
499
  {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@dcodegroup-au/page-builder",
3
- "version": "0.2.5",
3
+ "version": "0.2.6",
4
4
  "exports": {
5
5
  ".": {
6
6
  "import": "./dist/page-builder.es.js"
@@ -0,0 +1,3 @@
1
+ <svg width="56" height="56" viewBox="0 0 56 56" fill="none" xmlns="http://www.w3.org/2000/svg">
2
+ <path fill-rule="evenodd" clip-rule="evenodd" d="M28 56C43.464 56 56 43.464 56 28C56 12.536 43.464 0 28 0C12.536 0 0 12.536 0 28C0 43.464 12.536 56 28 56ZM23.625 38.2705L39.375 29.4672C40.5417 28.8151 40.5417 27.1849 39.375 26.5328L23.625 17.7295C22.4583 17.0774 21 17.8925 21 19.1967V36.8033C21 38.1075 22.4583 38.9226 23.625 38.2705Z" fill="white"/>
3
+ </svg>
@@ -41,6 +41,7 @@
41
41
  <div class="flex h-full flex-1 flex-col rounded-xl bg-gray-50 px-6 py-5 mb-20">
42
42
  <Instructions v-if="!selected" />
43
43
  <component
44
+ class="min-h-[90vh]"
44
45
  :is="currentComponent"
45
46
  :key="selected?.sectionIndex + selected?.componentIndex"
46
47
  :data="selected"
@@ -72,6 +73,8 @@ import VLinks from "@/components/builders/VLinks.vue";
72
73
  import VHeader from "@/components/builders/VHeader.vue";
73
74
  import VLogos from "@/components/builders/VLogos.vue";
74
75
  import VCollectionCarousel from "@/components/builders/VCollectionCarousel.vue";
76
+ import VNewsGrid from "@/components/builders/VNewsGrid.vue";
77
+ import VVideoGrid from "@/components/builders/VVideoGrid.vue";
75
78
 
76
79
  const emit = defineEmits(["save", "close"]);
77
80
  const props = defineProps({
@@ -98,6 +101,8 @@ const componentMaps = ref({
98
101
  link_grid: markRaw(VLinks),
99
102
  logos: markRaw(VLogos),
100
103
  carousel: markRaw(VCollectionCarousel),
104
+ news_grid: markRaw(VNewsGrid),
105
+ video_grid: markRaw(VVideoGrid),
101
106
  });
102
107
 
103
108
  if (!openStates.value) {
@@ -16,6 +16,7 @@ import QuickLinks from "@/components/presenters/modules/QuickLinks.vue";
16
16
  import VTabs from "@/components/presenters/modules/VTabs.vue";
17
17
  import LogoCloud from "@/components/presenters/modules/LogoCloud.vue";
18
18
  import CollectionCarousel from "@/components/presenters/modules/CollectionCarousel.vue";
19
+ import CollectionGrid from "@/components/presenters/modules/CollectionGrid.vue";
19
20
 
20
21
  const props = defineProps({
21
22
  page: {
@@ -30,6 +31,7 @@ const componentMaps = ref({
30
31
  tabs: markRaw(VTabs),
31
32
  logo: markRaw(LogoCloud),
32
33
  collection_carousel: markRaw(CollectionCarousel),
34
+ collection_grid: markRaw(CollectionGrid),
33
35
  });
34
36
 
35
37
  const currentComponent = (section) => {
@@ -0,0 +1,55 @@
1
+ <template>
2
+ <div class="flex flex-col">
3
+ <div class="flex flex-col gap-4 mb-4 border-b border-gray-200 pb-4">
4
+ <p class="text-lg font-semibold text-gray-900 border-b border-gray-200 pb-4">
5
+ Grid
6
+ </p>
7
+ <VToggle name="show" v-model="componentData.button.show" title="Show Button" />
8
+ <input-wrapper
9
+ is-vertical
10
+ field="title"
11
+ label-text="Title *"
12
+ class="w-full mb-4"
13
+ :value="componentData.button.title"
14
+ :limit="20"
15
+ >
16
+ <input
17
+ v-model="componentData.button.title"
18
+ name="title"
19
+ type="text"
20
+ placeholder="Title"
21
+ :maxlength="20"
22
+ class="border-1 border-solid border-gray-300 rounded-lg bg-white w-full"
23
+ />
24
+ </input-wrapper>
25
+ <linked-to
26
+ v-if="componentData?.button"
27
+ label="Link to"
28
+ name="button"
29
+ v-model:type="componentData.button.type"
30
+ v-model:url="componentData.button.url"
31
+ v-model:openInNewTab="componentData.button.open_in_new_tab"
32
+ :sites="sites"
33
+ />
34
+ </div>
35
+ <div>
36
+ <h3 v-if="componentData.content?.label" class="text-base text-gray-900 font-semibold">{{ componentData.content.label }}</h3>
37
+ <p v-if="componentData.content?.supportive_text" class="text-gray-600 text-base font-normal mt-2">{{ componentData.content.supportive_text }}</p>
38
+ </div>
39
+ </div>
40
+ </template>
41
+ <script setup>
42
+ import {ref} from "vue";
43
+ import {defaultProps} from "@/components/helpers/defaultProps";
44
+ import LinkedTo from "@/components/common/LinkedTo.vue";
45
+ import InputWrapper from "@/components/common/InputWrapper.vue";
46
+ import VToggle from "@/components/common/VToggle.vue";
47
+
48
+ const emit = defineEmits(["update"]);
49
+
50
+ const props = defineProps({
51
+ ...defaultProps,
52
+ });
53
+
54
+ const componentData = ref(props.data.component);
55
+ </script>
@@ -0,0 +1,99 @@
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 }} videos
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
+ Video #{{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
+ type="video"
44
+ height="h-[250px]"
45
+ v-model="item.video"
46
+ />
47
+ </div>
48
+ </div>
49
+ </div>
50
+ </div>
51
+ <VModal ref="modalRef" entity="logo" :callback="deleteCallback"></VModal>
52
+ </template>
53
+ <script setup>
54
+ import {ref, inject} from "vue";
55
+ import PlusIcon from "@/assets/img/icons/plus.svg";
56
+ import {defaultProps} from "@/components/helpers/defaultProps";
57
+ import ActionMenu from "@/components/common/ActionMenu.vue";
58
+ import VModal from "@/components/common/VModal.vue";
59
+ import DefaultFileUpload from "@/components/common/FileUpload.vue";
60
+
61
+ // Inject the FileUpload component or fallback to the default one
62
+ const VFileUpload = inject("VFileUpload", DefaultFileUpload);
63
+
64
+ const emit = defineEmits(["update"]);
65
+
66
+ const props = defineProps({
67
+ ...defaultProps,
68
+ });
69
+
70
+ const componentData = ref(props.data.component);
71
+ const modalRef = ref(null);
72
+ const lastItemRef = ref(null);
73
+ const deleteItemIndex = ref(null);
74
+
75
+ function addItem() {
76
+ if (!componentData.value.hasOwnProperty('data')) {
77
+ componentData.value.data = [];
78
+ }
79
+
80
+ if (componentData.value.data?.length >= componentData.value.max_items) {
81
+ return;
82
+ }
83
+ componentData.value.data?.push({
84
+ video: null,
85
+ });
86
+
87
+ emit("update", false);
88
+ }
89
+
90
+ const handleDeleteItem = (index) => {
91
+ deleteItemIndex.value = index;
92
+ modalRef?.value?.open(index);
93
+ }
94
+
95
+ const deleteCallback = (index) => {
96
+ componentData.value.data?.splice(index, 1);
97
+ emit("update", false);
98
+ };
99
+ </script>
@@ -22,13 +22,13 @@
22
22
  />
23
23
  </div>
24
24
  <div v-if="slide.tags && slide.tags.length" class="flex gap-2">
25
- <span
26
- v-for="tag in slide.tags"
27
- :key="tag"
28
- class="bg-navy-50 border border-navy-200 text-navy-700 px-2 py-0.5 rounded-[6px] text-sm"
29
- >
30
- {{ tag }}
31
- </span>
25
+ <span
26
+ v-for="tag in slide.tags"
27
+ :key="tag"
28
+ class="bg-navy-50 border border-navy-200 text-navy-700 px-2 py-0.5 rounded-[6px] text-sm"
29
+ >
30
+ {{ tag }}
31
+ </span>
32
32
  </div>
33
33
  <h3 class="text-[24px] font-semibold text-gray-900 mb-3 leading-[23px] mt-3">
34
34
  {{ slide?.title }}
@@ -0,0 +1,121 @@
1
+ <template>
2
+ <div class="flex flex-col items-center gap-6 pb-[40px] pt-[24px]">
3
+ <div class="grid grid-cols-1 md:grid-cols-3 gap-6 w-full">
4
+ <div v-for="(card, index) in items" :key="index" class="card bg-white sm:p-0 px-4">
5
+ <div v-if="component.type === 'video_grid'" class="relative group">
6
+ <video
7
+ @click="togglePlayPause(index)"
8
+ :ref="(el) => setVideoRef(el, index)"
9
+ class="w-full h-[281px] object-cover rounded-[16px]"
10
+ width="640"
11
+ height="360"
12
+ preload="metadata"
13
+ >
14
+ <source :src="`${card.video}#t=2`" />
15
+ </video>
16
+ <PlayButton
17
+ v-if="!isPlaying[index]"
18
+ class="cursor-pointer absolute top-1/2 left-1/2 transform -translate-x-1/2 -translate-y-1/2 group-hover:opacity-100 transition-opacity"
19
+ @click="togglePlayPause(index)">
20
+
21
+ </PlayButton>
22
+ </div>
23
+ <img
24
+ v-else
25
+ :src="card.image"
26
+ alt="Card Image"
27
+ class="w-full h-[281px] object-cover rounded-[16px] mb-4"
28
+ />
29
+ <div class="flex flex-col gap-2 items-start text-left w-full">
30
+ <div v-if="card.categories && card.categories.length" class="flex gap-2">
31
+ <span
32
+ v-for="tag in card.categories"
33
+ :key="tag"
34
+ class="border px-2 py-0.5 rounded-[6px] text-sm"
35
+ :class="{
36
+ 'bg-navy-50 border-navy-200 text-navy-700': tag.style === 'navy',
37
+ 'bg-brand-50 border-brand-200 text-brand-700': tag.style === 'brand',
38
+ 'bg-orange-50 border-orange-200 text-orange-700': tag.style === 'orange',
39
+ }"
40
+ >
41
+ {{ tag.name }}
42
+ </span>
43
+ </div>
44
+ <a
45
+ v-if="card?.link"
46
+ :href="card.link"
47
+ class="text-gray-900 text-lg font-semibold hover:underline block mb-2 w-full"
48
+ target="_blank"
49
+ rel="noopener noreferrer"
50
+ >
51
+ <div class="flex justify-between">
52
+ <span class="w-5/6 leading-[28px]">{{ card.title }}</span>
53
+ <ArrowUpRight class="w-5 h-5 mt-1"></ArrowUpRight>
54
+ </div>
55
+ </a>
56
+ <p class="text-gray-500 text-sm mb-4">{{ card.dateTime }}</p>
57
+ </div>
58
+ </div>
59
+ </div>
60
+ <div v-if="Object.keys(button).length && button?.show" class="flex justify-center">
61
+ <a
62
+ class="border-brand-300 hover:border-brand-700 border text-brand-700 h-[44px] rounded-full px-[14px] py-[10px] inline-flex gap-1.5 items-center font-semibold text-base"
63
+ :href="button.url"
64
+ :target="button.is_new_tab ? '_blank' : ''"
65
+ >
66
+ {{ button.title }}
67
+ <ArrowUpRight class="w-5 h-5"></ArrowUpRight>
68
+ </a>
69
+ </div>
70
+ </div>
71
+ </template>
72
+
73
+ <script setup>
74
+ import { ref } from "vue";
75
+ import ArrowUpRight from "@/assets/img/icons/arrow-up-right.svg";
76
+ import PlayButton from "@/assets/svg/play.svg";
77
+
78
+ const props = defineProps({
79
+ component: {
80
+ required: true,
81
+ type: Object,
82
+ },
83
+ });
84
+
85
+ let items = [];
86
+ if (props.component.type === 'news_grid') {
87
+ items = [...props.component.content?.items || []];
88
+ } else {
89
+ items = [...props.component?.data || []];
90
+ }
91
+
92
+ const button = props.component.button || {};
93
+
94
+ const isPlaying = ref(items.map(() => false));
95
+ const videoRefs = ref(items.map(() => null));
96
+
97
+ const togglePlayPause = (index) => {
98
+ const video = videoRefs.value[index];
99
+ if (!video) return;
100
+
101
+ if (video.paused) {
102
+ video.play();
103
+ isPlaying.value[index] = true;
104
+ } else {
105
+ video.pause();
106
+ isPlaying.value[index] = false;
107
+ }
108
+ };
109
+
110
+ const setVideoRef = (el, index) => {
111
+ if (el) {
112
+ videoRefs.value[index] = el;
113
+ }
114
+ };
115
+ </script>
116
+
117
+ <style scoped>
118
+ .card {
119
+ @apply flex flex-col items-center text-center;
120
+ }
121
+ </style>
@@ -1,5 +1,5 @@
1
1
  <template>
2
- <div class="flex flex-col" :class="{'items-center': component?.center}">
2
+ <div class="flex flex-col sm:px-0 px-4" :class="{'items-center': component?.center}">
3
3
  <p
4
4
  v-if="component?.title"
5
5
  class="pb-4 text-4xl font-semibold text-white"
@@ -8,7 +8,7 @@
8
8
  <p
9
9
  v-if="component?.supporting_text"
10
10
  class="text-navy-25 text-xl font-normal leading-[30px]"
11
- :class="{'!text-navy-900': component?.dark}"
11
+ :class="{'!text-gray-600': component?.dark}"
12
12
  >
13
13
  {{ component?.supporting_text }}</p>
14
14
  </div>
@@ -0,0 +1,41 @@
1
+ <template>
2
+ <div class="overflow-hidden">
3
+ <div class="max-w-[1400px] mx-auto w-full pt-[40px]">
4
+ <template v-for="(component, index) in section.components" :key="index">
5
+ <component
6
+ :is="currentComponent(component)"
7
+ :component="component"
8
+ ></component>
9
+ </template>
10
+ </div>
11
+ </div>
12
+ </template>
13
+
14
+ <script setup>
15
+ import { ref, markRaw } from "vue";
16
+ import VHeaderPresenter from "@/components/presenters/components/VHeaderPresenter.vue";
17
+ import VCollectionGridPresenter from "@/components/presenters/components/VCollectionGridPresenter.vue";
18
+
19
+ const props = defineProps({
20
+ section: {
21
+ required: true,
22
+ type: Object,
23
+ },
24
+ });
25
+
26
+ const section = ref(props.section);
27
+ const componentMaps = ref({
28
+ header: markRaw(VHeaderPresenter),
29
+ news_grid: markRaw(VCollectionGridPresenter),
30
+ video_grid: markRaw(VCollectionGridPresenter),
31
+ });
32
+
33
+ const currentComponent = (component) => {
34
+ if (!component?.type) {
35
+ return '';
36
+ }
37
+
38
+ return componentMaps.value[component?.type];
39
+ };
40
+
41
+ </script>
@@ -55,6 +55,11 @@ module.exports = {
55
55
  600: '#D92D20',
56
56
  700: 'rgba(180, 35, 24, 1)',
57
57
  },
58
+ orange: {
59
+ 700: '#B93815',
60
+ 200: '#F9DBAF',
61
+ 50: '#FEF6EE',
62
+ },
58
63
  warning: {
59
64
  50: 'rgba(255, 250, 235, 1)',
60
65
  200: 'rgba(254, 223, 137, 1)',