@dcodegroup-au/page-builder 0.1.1 → 0.1.2
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.
- package/dist/page-builder.css +1 -1
- package/dist/page-builder.es.js +4056 -3917
- package/dist/page-builder.umd.js +44 -44
- package/example/src/App.vue +92 -26
- package/example/src/main.js +2 -2
- package/package.json +1 -1
- package/src/assets/css/style.css +1 -1
- package/src/components/{SlideEdit.vue → ItemEdit.vue} +17 -11
- package/src/components/PageBuilder.vue +3 -2
- package/src/components/PageRender.vue +2 -0
- package/src/components/builders/{VSliders.vue → VItems.vue} +8 -6
- package/src/components/builders/VLinks.vue +9 -5
- package/src/components/helpers/common.js +61 -0
- package/src/components/index.js +2 -2
- package/src/components/presenters/components/VHeaderPresenter.vue +13 -4
- package/src/components/presenters/components/VLinkPresenter.vue +1 -1
- package/src/components/presenters/components/VVerticalTabPresenter.vue +85 -0
- package/src/components/presenters/modules/VTabs.vue +41 -0
- package/tailwind.config.js +1 -0
- package/src/components/builders/PageBuilderVerticalTabs.vue +0 -87
package/example/src/App.vue
CHANGED
|
@@ -4,12 +4,10 @@ const page = {
|
|
|
4
4
|
title: "Page Builder",
|
|
5
5
|
sections: [
|
|
6
6
|
{
|
|
7
|
-
id: 1,
|
|
8
7
|
title: "Hero header",
|
|
9
8
|
type: "header",
|
|
10
9
|
components: [
|
|
11
10
|
{
|
|
12
|
-
id: 1,
|
|
13
11
|
name: "Sliders",
|
|
14
12
|
type: "sliders",
|
|
15
13
|
supportive_text: "Manage the slides in the slider.",
|
|
@@ -99,7 +97,6 @@ const page = {
|
|
|
99
97
|
]
|
|
100
98
|
},
|
|
101
99
|
{
|
|
102
|
-
id: 2,
|
|
103
100
|
name: "Links",
|
|
104
101
|
type: "links",
|
|
105
102
|
supportive_text: "Manage the slides in the slider.",
|
|
@@ -122,12 +119,10 @@ const page = {
|
|
|
122
119
|
]
|
|
123
120
|
},
|
|
124
121
|
{
|
|
125
|
-
id: 2,
|
|
126
122
|
title: "Logo cloud",
|
|
127
123
|
type: "logo",
|
|
128
124
|
components: [
|
|
129
125
|
{
|
|
130
|
-
id: 1,
|
|
131
126
|
name: "Section header",
|
|
132
127
|
type: "header",
|
|
133
128
|
is_public: true,
|
|
@@ -140,74 +135,126 @@ const page = {
|
|
|
140
135
|
]
|
|
141
136
|
},
|
|
142
137
|
{
|
|
143
|
-
id: 3,
|
|
144
138
|
title: "Vertical tabs module",
|
|
145
|
-
type: "
|
|
139
|
+
type: "tabs",
|
|
146
140
|
components: [
|
|
147
141
|
{
|
|
148
|
-
id: 1,
|
|
149
142
|
name: "Section header",
|
|
150
143
|
type: "header",
|
|
151
|
-
|
|
144
|
+
title: 'Rich with resources',
|
|
145
|
+
center: true,
|
|
146
|
+
dark: true,
|
|
147
|
+
supporting_text: 'Comprehensive free and paid resources covering every aspect of early childhood education.',
|
|
152
148
|
},
|
|
153
149
|
{
|
|
154
|
-
id: 2,
|
|
155
150
|
name: "Vertical tabs",
|
|
156
151
|
type: "vertical_tabs",
|
|
152
|
+
supportive_text: "Manage the tabs",
|
|
153
|
+
max_items: 5,
|
|
154
|
+
public: true,
|
|
155
|
+
data: [
|
|
156
|
+
{
|
|
157
|
+
title: "Free Downloadable Resources",
|
|
158
|
+
description: "Explore a variety of no-cost tools and information available to everyone.",
|
|
159
|
+
public: true,
|
|
160
|
+
featured_image: 'https://beta-frontend.elaa.org.au/img/homepage/asset_free_resources.png',
|
|
161
|
+
primary_button: {
|
|
162
|
+
show: true,
|
|
163
|
+
name: 'Linked to',
|
|
164
|
+
label: 'View all',
|
|
165
|
+
url: 'google.com', // external could be an url
|
|
166
|
+
type: 'external-page',
|
|
167
|
+
is_new_tab: true,
|
|
168
|
+
},
|
|
169
|
+
},
|
|
170
|
+
{
|
|
171
|
+
title: "Paid Resources & Subscriptions",
|
|
172
|
+
description: "Access premium content and exclusive materials with our subscription services.",
|
|
173
|
+
public: true,
|
|
174
|
+
featured_image: 'https://beta-frontend.elaa.org.au/img/homepage/asset_paid_resources.png',
|
|
175
|
+
primary_button: {
|
|
176
|
+
show: true,
|
|
177
|
+
name: 'Linked to',
|
|
178
|
+
label: 'View all',
|
|
179
|
+
url: 'google.com', // external could be an url
|
|
180
|
+
type: 'external-page',
|
|
181
|
+
is_new_tab: true,
|
|
182
|
+
},
|
|
183
|
+
},
|
|
184
|
+
{
|
|
185
|
+
title: "Video Library",
|
|
186
|
+
description: "Watch quick, informative videos (10-15 mins each) available for free.",
|
|
187
|
+
public: true,
|
|
188
|
+
featured_image: 'https://beta-frontend.elaa.org.au/img/homepage/asset_video.png',
|
|
189
|
+
primary_button: {
|
|
190
|
+
show: true,
|
|
191
|
+
name: 'Linked to',
|
|
192
|
+
label: 'View all',
|
|
193
|
+
url: 'google.com', // external could be an url
|
|
194
|
+
type: 'external-page',
|
|
195
|
+
is_new_tab: true,
|
|
196
|
+
},
|
|
197
|
+
},
|
|
198
|
+
{
|
|
199
|
+
title: "Self-paced Modules",
|
|
200
|
+
description: "Engage with our flexible, on-demand training modules at your own pace.",
|
|
201
|
+
public: true,
|
|
202
|
+
featured_image: 'https://beta-frontend.elaa.org.au/img/homepage/asset_self_paced.png',
|
|
203
|
+
primary_button: {
|
|
204
|
+
show: true,
|
|
205
|
+
name: 'Linked to',
|
|
206
|
+
label: 'View all',
|
|
207
|
+
url: 'google.com', // external could be an url
|
|
208
|
+
type: 'external-page',
|
|
209
|
+
is_new_tab: true,
|
|
210
|
+
},
|
|
211
|
+
},
|
|
212
|
+
]
|
|
157
213
|
}
|
|
158
214
|
]
|
|
159
215
|
},
|
|
160
216
|
{
|
|
161
|
-
id: 4,
|
|
162
217
|
title: "Collection grid",
|
|
163
218
|
type: "collection_grid",
|
|
164
219
|
components: [
|
|
165
220
|
{
|
|
166
|
-
id: 1,
|
|
167
221
|
name: "Section header",
|
|
168
222
|
type: "header",
|
|
169
|
-
|
|
223
|
+
title: 'Services',
|
|
224
|
+
supporting_text: 'Our knowledge and expertise of the early childhood sector enables ELAA to provide expert professional advice and support.',
|
|
170
225
|
},
|
|
171
226
|
{
|
|
172
|
-
id: 2,
|
|
173
227
|
name: "Grid",
|
|
174
228
|
type: "grid",
|
|
175
229
|
}
|
|
176
230
|
]
|
|
177
231
|
},
|
|
178
232
|
{
|
|
179
|
-
id: 4,
|
|
180
233
|
title: "Collection carousel",
|
|
181
234
|
type: "collection_carousel",
|
|
182
235
|
components: [
|
|
183
236
|
{
|
|
184
|
-
id: 1,
|
|
185
237
|
name: "Section header",
|
|
186
238
|
type: "header",
|
|
187
|
-
|
|
239
|
+
title: 'Services',
|
|
240
|
+
supporting_text: 'Our knowledge and expertise of the early childhood sector enables ELAA to provide expert professional advice and support.',
|
|
188
241
|
},
|
|
189
242
|
{
|
|
190
|
-
id: 2,
|
|
191
243
|
name: "Carousel",
|
|
192
244
|
type: "carousel",
|
|
193
245
|
}
|
|
194
246
|
]
|
|
195
247
|
},
|
|
196
248
|
{
|
|
197
|
-
id: 4,
|
|
198
249
|
title: "Quick links",
|
|
199
250
|
type: "quick_links",
|
|
200
251
|
components: [
|
|
201
252
|
{
|
|
202
|
-
id: 1,
|
|
203
|
-
name: "Section header",
|
|
204
253
|
type: "header",
|
|
205
|
-
is_public: true,
|
|
206
254
|
title: 'Services',
|
|
207
255
|
supporting_text: 'Our knowledge and expertise of the early childhood sector enables ELAA to provide expert professional advice and support.',
|
|
208
256
|
},
|
|
209
257
|
{
|
|
210
|
-
id: 2,
|
|
211
258
|
name: "Link grid",
|
|
212
259
|
type: "link_grid",
|
|
213
260
|
supportive_text: "This section can contain up to 10 links.",
|
|
@@ -271,14 +318,29 @@ const slide = {
|
|
|
271
318
|
},
|
|
272
319
|
}
|
|
273
320
|
|
|
321
|
+
const tab = {
|
|
322
|
+
title: "Free Downloadable Resources",
|
|
323
|
+
description: "Explore a variety of no-cost tools and information available to everyone.",
|
|
324
|
+
public: true,
|
|
325
|
+
featured_image: 'https://beta-frontend.elaa.org.au/img/homepage/asset_free_resources.png',
|
|
326
|
+
primary_button: {
|
|
327
|
+
show: true,
|
|
328
|
+
name: 'Linked to',
|
|
329
|
+
label: 'View all',
|
|
330
|
+
url: 'google.com', // external could be an url
|
|
331
|
+
type: 'external-page',
|
|
332
|
+
is_new_tab: true,
|
|
333
|
+
},
|
|
334
|
+
}
|
|
335
|
+
|
|
274
336
|
const sites = [
|
|
275
337
|
{
|
|
276
338
|
name: "Site 1",
|
|
277
|
-
|
|
339
|
+
href: "/site-1",
|
|
278
340
|
},
|
|
279
341
|
{
|
|
280
342
|
name: "Site 2",
|
|
281
|
-
|
|
343
|
+
href: "/site-2",
|
|
282
344
|
}
|
|
283
345
|
]
|
|
284
346
|
</script>
|
|
@@ -291,7 +353,11 @@ const sites = [
|
|
|
291
353
|
</div>
|
|
292
354
|
<div style="margin: 40px 0">
|
|
293
355
|
<h1 style="margin-bottom: 20px; font-size: 50px;">Slider Edit</h1>
|
|
294
|
-
<
|
|
356
|
+
<ItemEdit :item="slide" :sites="sites"/>
|
|
357
|
+
</div>
|
|
358
|
+
<div style="margin: 40px 0">
|
|
359
|
+
<h1 style="margin-bottom: 20px; font-size: 50px;">Vertical Tab Edit</h1>
|
|
360
|
+
<ItemEdit :item="tab" :sites="sites"/>
|
|
295
361
|
</div>
|
|
296
362
|
<div style="margin: 40px 0">
|
|
297
363
|
<h1 style="margin-bottom: 20px; font-size: 50px;">Page Builder</h1>
|
package/example/src/main.js
CHANGED
|
@@ -1,11 +1,11 @@
|
|
|
1
1
|
import { createApp } from 'vue'
|
|
2
2
|
|
|
3
3
|
import App from './App.vue'
|
|
4
|
-
import { PageBuilder,
|
|
4
|
+
import { PageBuilder, ItemEdit, PageRender } from '../../dist/page-builder.es.js'
|
|
5
5
|
import '../../dist/page-builder.css'
|
|
6
6
|
|
|
7
7
|
const app = createApp(App)
|
|
8
|
-
app.component('
|
|
8
|
+
app.component('ItemEdit', ItemEdit)
|
|
9
9
|
app.component('PageBuilder', PageBuilder)
|
|
10
10
|
app.component('PageRender', PageRender)
|
|
11
11
|
|
package/package.json
CHANGED
package/src/assets/css/style.css
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
<template>
|
|
2
|
-
<div class="
|
|
2
|
+
<div class="item-edit">
|
|
3
3
|
<div class="flex items-start gap-4 px-6 pt-4 h-full">
|
|
4
4
|
<div class="flex flex-1 flex-col gap-4">
|
|
5
5
|
<card title="Description">
|
|
@@ -33,7 +33,7 @@
|
|
|
33
33
|
</input-wrapper>
|
|
34
34
|
</div>
|
|
35
35
|
</card>
|
|
36
|
-
<card title="Primary button">
|
|
36
|
+
<card v-if="form.hasOwnProperty('primary_button')" :title="item?.primary_button?.name ?? 'Primary button'">
|
|
37
37
|
<div class="flex flex-col gap-8">
|
|
38
38
|
<VToggle name="show" v-model="form.primary_button.show" title="Show Button" />
|
|
39
39
|
<input-wrapper
|
|
@@ -62,7 +62,7 @@
|
|
|
62
62
|
/>
|
|
63
63
|
</div>
|
|
64
64
|
</card>
|
|
65
|
-
<card title="Secondary button">
|
|
65
|
+
<card v-if="form.hasOwnProperty('secondary_button')" :title="item?.secondary_button?.name ?? 'Secondary button'">
|
|
66
66
|
<div class="flex flex-col gap-4">
|
|
67
67
|
<VToggle name="show" v-model="form.secondary_button.show" title="Show Button" />
|
|
68
68
|
<template v-if="form.secondary_button?.show">
|
|
@@ -132,7 +132,7 @@ import axios from "axios";
|
|
|
132
132
|
const emit = defineEmits(["update"]);
|
|
133
133
|
|
|
134
134
|
const props = defineProps({
|
|
135
|
-
|
|
135
|
+
item: {
|
|
136
136
|
type: Object,
|
|
137
137
|
required: true,
|
|
138
138
|
},
|
|
@@ -151,19 +151,25 @@ const props = defineProps({
|
|
|
151
151
|
});
|
|
152
152
|
|
|
153
153
|
const form = ref({
|
|
154
|
-
public: props.
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
secondary_button: props.slide.secondary_button,
|
|
159
|
-
title: props.slide.title,
|
|
154
|
+
public: props.item.public,
|
|
155
|
+
title: props.item.title,
|
|
156
|
+
description: props.item.description,
|
|
157
|
+
featured_image: props.item.featured_image,
|
|
160
158
|
});
|
|
161
159
|
|
|
160
|
+
if (props.item.hasOwnProperty('primary_button')) {
|
|
161
|
+
form.value.primary_button = props.item.primary_button;
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
if (props.item.hasOwnProperty('secondary_button')) {
|
|
165
|
+
form.value.secondary_button = props.item.secondary_button;
|
|
166
|
+
}
|
|
167
|
+
|
|
162
168
|
const save = () => {
|
|
163
169
|
emit("update", form.value);
|
|
164
170
|
|
|
165
171
|
if (props.saveEndpoint) {
|
|
166
|
-
axios.post(props.saveEndpoint, form.value).then(() => {
|
|
172
|
+
axios.post(props.saveEndpoint, form.value).then (() => {
|
|
167
173
|
if (props.cancelEndpoint) {
|
|
168
174
|
window.location.href = props.cancelEndpoint;
|
|
169
175
|
}
|
|
@@ -67,7 +67,7 @@ import { ref, markRaw, computed } from "vue";
|
|
|
67
67
|
import ChevronRight from "@/assets/img/icons/chevron-right.svg";
|
|
68
68
|
import ChevronDown from "@/assets/img/icons/chevron-down.svg";
|
|
69
69
|
import Instructions from "@/components/builders/Instructions.vue";
|
|
70
|
-
import
|
|
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
73
|
|
|
@@ -89,7 +89,8 @@ const openStates = ref(JSON.parse(window.localStorage.getItem("pageBuilderOpenSt
|
|
|
89
89
|
const sections = ref(props.modelValue?.sections ?? []);
|
|
90
90
|
let selected = ref(null);
|
|
91
91
|
const componentMaps = ref({
|
|
92
|
-
sliders: markRaw(
|
|
92
|
+
sliders: markRaw(VItems),
|
|
93
|
+
vertical_tabs: markRaw(VItems),
|
|
93
94
|
links: markRaw(VLinks),
|
|
94
95
|
header: markRaw(VHeader),
|
|
95
96
|
link_grid: markRaw(VLinks),
|
|
@@ -13,6 +13,7 @@
|
|
|
13
13
|
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
|
+
import VTabs from "@/components/presenters/modules/VTabs.vue";
|
|
16
17
|
|
|
17
18
|
const props = defineProps({
|
|
18
19
|
page: {
|
|
@@ -24,6 +25,7 @@ const props = defineProps({
|
|
|
24
25
|
const componentMaps = ref({
|
|
25
26
|
header: markRaw(HeroHeader),
|
|
26
27
|
quick_links: markRaw(QuickLinks),
|
|
28
|
+
tabs: markRaw(VTabs),
|
|
27
29
|
});
|
|
28
30
|
|
|
29
31
|
const currentComponent = (section) => {
|
|
@@ -11,8 +11,8 @@
|
|
|
11
11
|
<div class="flex flex-col gap-3">
|
|
12
12
|
<div class="flex justify-between">
|
|
13
13
|
<div class="flex flex-col gap-1">
|
|
14
|
-
<div class="font-semibold text-gray-900">
|
|
15
|
-
<div class="text-sm text-gray-600">This
|
|
14
|
+
<div class="font-semibold text-gray-900">{{ parseName(type) }}</div>
|
|
15
|
+
<div class="text-sm text-gray-600">This {{ singularize(type) }} can contain up to {{ dataRef.max_items }} {{ parseName(type, false) }}</div>
|
|
16
16
|
</div>
|
|
17
17
|
<div>
|
|
18
18
|
<button
|
|
@@ -34,7 +34,7 @@
|
|
|
34
34
|
<div class="flex flex-1 items-center justify-between relative" @click="toggleSlide(index);">
|
|
35
35
|
<div class="flex flex-1 flex-col cursor-pointer" @click="edit(item, index)">
|
|
36
36
|
<div class="text-xs text-gray-600">
|
|
37
|
-
|
|
37
|
+
{{ singularize(parseName(type)) }} #{{ index + 1 }}
|
|
38
38
|
</div>
|
|
39
39
|
<div class="text-sm font-medium text-gray-900">
|
|
40
40
|
{{ item.title }}
|
|
@@ -46,13 +46,14 @@
|
|
|
46
46
|
</div>
|
|
47
47
|
</div>
|
|
48
48
|
</div>
|
|
49
|
-
<VModal ref="modalRef" entity="
|
|
49
|
+
<VModal ref="modalRef" :entity="singularize(type)" :callback="deleteCallback"></VModal>
|
|
50
50
|
</template>
|
|
51
51
|
<script setup>
|
|
52
52
|
import {ref} from "vue";
|
|
53
53
|
import PlusIcon from "@/assets/img/icons/plus.svg";
|
|
54
|
-
import {defaultProps} from "@/components/helpers/defaultProps
|
|
55
|
-
import {
|
|
54
|
+
import { defaultProps } from "@/components/helpers/defaultProps";
|
|
55
|
+
import { singularize, parseName } from "@/components/helpers/common";
|
|
56
|
+
import { createSlide } from "@/components/helpers/pageBuilderFactory";
|
|
56
57
|
import ActionMenu from "@/components/common/ActionMenu.vue";
|
|
57
58
|
import VModal from "@/components/common/VModal.vue";
|
|
58
59
|
|
|
@@ -62,6 +63,7 @@ const props = defineProps({
|
|
|
62
63
|
});
|
|
63
64
|
|
|
64
65
|
const dataRef = ref(props.data.component);
|
|
66
|
+
const type = dataRef.value.type;
|
|
65
67
|
const modalRef = ref(null);
|
|
66
68
|
|
|
67
69
|
const openSlideStates = ref(JSON.parse(window.localStorage.getItem("openSlideStates")));
|
|
@@ -13,7 +13,7 @@
|
|
|
13
13
|
<a
|
|
14
14
|
@click="addItem"
|
|
15
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
|
|
16
|
+
:class="{ 'border-gray-100 bg-gray-100 !text-gray-400 hover:bg-gray-100': componentData.data?.length >= componentData.max_items }"
|
|
17
17
|
>
|
|
18
18
|
<PlusIcon class="h-5 w-5"></PlusIcon>
|
|
19
19
|
<span>Add</span>
|
|
@@ -72,7 +72,7 @@
|
|
|
72
72
|
<script setup>
|
|
73
73
|
import {ref, nextTick} from "vue";
|
|
74
74
|
import PlusIcon from "@/assets/img/icons/plus.svg";
|
|
75
|
-
import {defaultProps} from "@/components/helpers/defaultProps
|
|
75
|
+
import {defaultProps} from "@/components/helpers/defaultProps";
|
|
76
76
|
import {createLink} from "@/components/helpers/pageBuilderFactory";
|
|
77
77
|
import ActionMenu from "@/components/common/ActionMenu.vue";
|
|
78
78
|
import InputWrapper from "@/components/common/InputWrapper.vue";
|
|
@@ -91,10 +91,14 @@ const lastItemRef = ref(null);
|
|
|
91
91
|
const deleteItemIndex = ref(null);
|
|
92
92
|
|
|
93
93
|
function addItem() {
|
|
94
|
-
if (componentData.value.data
|
|
94
|
+
if (!componentData.value.hasOwnProperty('data')) {
|
|
95
|
+
componentData.value.data = [];
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
if (componentData.value.data?.length >= componentData.value.max_items) {
|
|
95
99
|
return;
|
|
96
100
|
}
|
|
97
|
-
componentData.value.data
|
|
101
|
+
componentData.value.data?.push(createLink());
|
|
98
102
|
|
|
99
103
|
nextTick(() => {
|
|
100
104
|
if (lastItemRef.value) {
|
|
@@ -111,7 +115,7 @@ const handleDeleteItem = (index) => {
|
|
|
111
115
|
}
|
|
112
116
|
|
|
113
117
|
const deleteCallback = (index) => {
|
|
114
|
-
componentData.value.data
|
|
118
|
+
componentData.value.data?.splice(index, 1);
|
|
115
119
|
emit("update", false);
|
|
116
120
|
};
|
|
117
121
|
</script>
|
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
export function pluralize(word, capitalizeFirst = false) {
|
|
2
|
+
const irregularPlurals = {
|
|
3
|
+
child: "children",
|
|
4
|
+
person: "people",
|
|
5
|
+
mouse: "mice",
|
|
6
|
+
foot: "feet",
|
|
7
|
+
tooth: "teeth",
|
|
8
|
+
goose: "geese",
|
|
9
|
+
};
|
|
10
|
+
|
|
11
|
+
let pluralWord = irregularPlurals[word] || `${word}s`;
|
|
12
|
+
|
|
13
|
+
if (capitalizeFirst) {
|
|
14
|
+
pluralWord = pluralWord.charAt(0).toUpperCase() + pluralWord.slice(1);
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
return pluralWord;
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
export function capitalize(str) {
|
|
21
|
+
return str.charAt(0).toUpperCase() + str.slice(1);
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
export function singularize(word, capitalizeFirst = false) {
|
|
25
|
+
const irregularSingulars = {
|
|
26
|
+
children: "child",
|
|
27
|
+
people: "person",
|
|
28
|
+
mice: "mouse",
|
|
29
|
+
feet: "foot",
|
|
30
|
+
teeth: "tooth",
|
|
31
|
+
geese: "goose",
|
|
32
|
+
};
|
|
33
|
+
|
|
34
|
+
word = word.replace(/_/g, ' ');
|
|
35
|
+
|
|
36
|
+
// Check if the word is in the irregular singulars dictionary
|
|
37
|
+
let singularWord = Object.keys(irregularSingulars).find(
|
|
38
|
+
(plural) => plural === word
|
|
39
|
+
)
|
|
40
|
+
? irregularSingulars[word]
|
|
41
|
+
: word.endsWith("s")
|
|
42
|
+
? word.slice(0, -1) // Remove the trailing "s" for regular pluralization
|
|
43
|
+
: word;
|
|
44
|
+
|
|
45
|
+
// Capitalize the first letter if the option is enabled
|
|
46
|
+
if (capitalizeFirst) {
|
|
47
|
+
singularWord = singularWord.charAt(0).toUpperCase() + singularWord.slice(1);
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
return singularWord;
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
export function parseName(str, capitalizeFirst = true) {
|
|
54
|
+
if (!str) return '';
|
|
55
|
+
const lastWord = str.split('_').pop();
|
|
56
|
+
if (capitalizeFirst) {
|
|
57
|
+
return capitalize(lastWord);
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
return lastWord;
|
|
61
|
+
}
|
package/src/components/index.js
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import '@/assets/css/style.css';
|
|
2
2
|
import PageBuilder from './PageBuilder.vue';
|
|
3
3
|
import PageRender from './PageRender.vue';
|
|
4
|
-
import
|
|
4
|
+
import ItemEdit from './ItemEdit.vue';
|
|
5
5
|
|
|
6
|
-
export { PageBuilder, PageRender,
|
|
6
|
+
export { PageBuilder, PageRender, ItemEdit };
|
|
@@ -1,12 +1,21 @@
|
|
|
1
1
|
<template>
|
|
2
|
-
<div class="flex flex-col">
|
|
3
|
-
<p
|
|
4
|
-
|
|
2
|
+
<div class="flex flex-col" :class="{'items-center': component?.center}">
|
|
3
|
+
<p
|
|
4
|
+
v-if="component?.title"
|
|
5
|
+
class="pb-4 text-4xl font-semibold text-white"
|
|
6
|
+
:class="{'!text-gray-900': component?.dark}"
|
|
7
|
+
>{{ component?.title }}</p>
|
|
8
|
+
<p
|
|
9
|
+
v-if="component?.supporting_text"
|
|
10
|
+
class="text-navy-25 text-xl font-normal leading-[30px]"
|
|
11
|
+
:class="{'!text-navy-900': component?.dark}"
|
|
12
|
+
>
|
|
13
|
+
{{ component?.supporting_text }}</p>
|
|
5
14
|
</div>
|
|
6
15
|
</template>
|
|
7
16
|
|
|
8
17
|
<script setup>
|
|
9
|
-
import {
|
|
18
|
+
import {ref} from "vue";
|
|
10
19
|
|
|
11
20
|
const props = defineProps({
|
|
12
21
|
component: {
|
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
<div class="flex justify-center" :class="{'!block': isLinkGrid}" v-for="link in component.data">
|
|
4
4
|
<a
|
|
5
5
|
:target="link.open_in_new_tab ? '_blank' : ''"
|
|
6
|
-
:href="link.url"
|
|
6
|
+
:href="`/${link.url}`"
|
|
7
7
|
class="text-white flex gap-1.5 items-center cursor-pointer mt-12 text-sm font-semibold hover:text-gray-300"
|
|
8
8
|
:class="{'bg-white border border-gray-300 rounded-full !text-gray-700 px-4 h-[44px] w-max !mt-0 !text-base hover:bg-gray-100': isLinkGrid}"
|
|
9
9
|
>
|
|
@@ -0,0 +1,85 @@
|
|
|
1
|
+
<template>
|
|
2
|
+
<div class="flex flex-col md:flex-row justify-center gap-4 items-stretch mt-6">
|
|
3
|
+
<!-- Left Column: Links -->
|
|
4
|
+
<div class="flex-1 w-full md:w-1/2 flex flex-col">
|
|
5
|
+
<div v-for="item in publicTabs" :key="item.title">
|
|
6
|
+
<div
|
|
7
|
+
class="flex-col flex gap-1.5 cursor-pointer hover:bg-navy-50 mb-4 group"
|
|
8
|
+
:class="{'border-l-4 border-brand-600': selectedItem === item}"
|
|
9
|
+
@click.prevent="selectItem(item)"
|
|
10
|
+
>
|
|
11
|
+
<div class="py-4 pl-4 md:pl-6">
|
|
12
|
+
<p
|
|
13
|
+
:class="{'text-gray-900': selectedItem === item, 'text-gray-600': selectedItem !== item}"
|
|
14
|
+
class="text-lg md:text-xl font-semibold mb-2 group-hover:text-gray-900"
|
|
15
|
+
>
|
|
16
|
+
{{ item.title }}
|
|
17
|
+
</p>
|
|
18
|
+
<p
|
|
19
|
+
:class="{'text-gray-700': selectedItem === item, 'text-gray-400': selectedItem !== item}"
|
|
20
|
+
class="text-sm md:text-md font-normal group-hover:text-gray-700"
|
|
21
|
+
>
|
|
22
|
+
{{ item.description }}
|
|
23
|
+
</p>
|
|
24
|
+
</div>
|
|
25
|
+
</div>
|
|
26
|
+
</div>
|
|
27
|
+
</div>
|
|
28
|
+
|
|
29
|
+
<!-- Right Column: Image and Button -->
|
|
30
|
+
<div class="flex-1 w-full md:w-1/2 bg-transparent flex flex-col items-center">
|
|
31
|
+
<transition name="fade" mode="out-in">
|
|
32
|
+
<div class="flex flex-col items-center" :key="selectedItem?.title">
|
|
33
|
+
<img
|
|
34
|
+
v-if="selectedItem?.featured_image"
|
|
35
|
+
:src="selectedItem.featured_image"
|
|
36
|
+
alt="Selected Item Image"
|
|
37
|
+
class="rounded-[20px] md:rounded-[40px] object-contain max-h-[200px] md:max-h-[387px] w-full"/>
|
|
38
|
+
<img
|
|
39
|
+
v-else
|
|
40
|
+
class="rounded-[20px] md:rounded-[40px] object-contain max-h-[200px] md:max-h-[387px] w-full"
|
|
41
|
+
src="@/assets/img/no_image_available.jpeg"
|
|
42
|
+
alt="No Image Available">
|
|
43
|
+
<a
|
|
44
|
+
v-if="selectedItem?.primary_button?.show"
|
|
45
|
+
class="text-sm md:text-md font-semibold text-brand-600 flex items-center gap-2 mt-4"
|
|
46
|
+
:target="selectedItem?.primary_button?.is_new_tab ? '_blank' : ''"
|
|
47
|
+
:href="selectedItem?.primary_button?.url">
|
|
48
|
+
{{ selectedItem?.primary_button?.label ?? 'N/A' }}
|
|
49
|
+
<ArrowUpRight class="w-4 h-4 md:w-5 md:h-5" />
|
|
50
|
+
</a>
|
|
51
|
+
</div>
|
|
52
|
+
</transition>
|
|
53
|
+
</div>
|
|
54
|
+
</div>
|
|
55
|
+
</template>
|
|
56
|
+
|
|
57
|
+
<script setup>
|
|
58
|
+
import { ref, onMounted } from "vue";
|
|
59
|
+
import ArrowUpRight from '@/assets/img/icons/arrow-up-right.svg';
|
|
60
|
+
|
|
61
|
+
const props = defineProps({
|
|
62
|
+
component: {
|
|
63
|
+
required: true,
|
|
64
|
+
type: Object,
|
|
65
|
+
},
|
|
66
|
+
});
|
|
67
|
+
|
|
68
|
+
const component = ref(props.component);
|
|
69
|
+
const selectedItem = ref(null);
|
|
70
|
+
|
|
71
|
+
// Filter to get only public tabs
|
|
72
|
+
const publicTabs = ref(component.value.data.filter((item) => item.public));
|
|
73
|
+
|
|
74
|
+
// Method to select an item
|
|
75
|
+
const selectItem = (item) => {
|
|
76
|
+
selectedItem.value = item;
|
|
77
|
+
};
|
|
78
|
+
|
|
79
|
+
// Preselect the first public item on component mount
|
|
80
|
+
onMounted(() => {
|
|
81
|
+
if (publicTabs.value.length > 0) {
|
|
82
|
+
selectItem(publicTabs.value[0]);
|
|
83
|
+
}
|
|
84
|
+
});
|
|
85
|
+
</script>
|