@dcodegroup-au/page-builder 0.0.2 → 0.0.4

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.
@@ -248,12 +248,18 @@ const sites = [
248
248
  </script>
249
249
 
250
250
  <template>
251
- <PageRender :page="page"/>
252
- <PageBuilder :page="page"/>
253
- <div class="flex">
254
- <div class="w-1/4">
255
- left panel
251
+ <div>
252
+ <div style="margin: 40px 0">
253
+ <h1 style="margin-bottom: 20px; font-size: 50px;">Page Render</h1>
254
+ <PageRender :page="page"/>
255
+ </div>
256
+ <div style="margin: 40px 0">
257
+ <h1 style="margin-bottom: 20px; font-size: 50px;">Slider Edit</h1>
258
+ <SlideEdit :slide="slide" :sites="sites"/>
259
+ </div>
260
+ <div style="margin: 40px 0">
261
+ <h1 style="margin-bottom: 20px; font-size: 50px;">Page Builder</h1>
262
+ <PageBuilder v-model="page"/>
256
263
  </div>
257
- <SlideEdit :slide="slide" :sites="sites"/>
258
264
  </div>
259
265
  </template>
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@dcodegroup-au/page-builder",
3
- "version": "0.0.2",
3
+ "version": "0.0.4",
4
4
  "exports": {
5
5
  ".": {
6
6
  "import": "./dist/page-builder.es.js"
@@ -12,7 +12,7 @@
12
12
  [multiple],
13
13
  textarea,
14
14
  select {
15
- @apply text-base block w-full rounded-lg border border-gray-300 !border-double placeholder:text-gray-400 focus:border-sky-300 focus:ring focus:ring-sky-200 focus:ring-opacity-50 py-2 px-3 font-normal;
15
+ @apply !text-base block !w-full !rounded-lg !border !border-gray-300 !border-double placeholder:text-gray-400 focus:border-sky-300 focus:ring focus:ring-sky-200 focus:ring-opacity-50 !py-2 !px-3 !font-normal;
16
16
  }
17
17
 
18
18
  .q-editor {
@@ -1,63 +1,78 @@
1
1
  <template>
2
- <div class="flex gap-4">
3
- <div class="flex w-[356px] flex-col gap-2">
4
- <div v-for="(parent, sectionIndex) in page.sections" class="border-b border-gray-200 pb-2">
5
- <div
6
- @click="openStates[sectionIndex] = !openStates[sectionIndex]"
7
- class="flex cursor-pointer items-center justify-between px-2 py-4"
8
- >
2
+ <div>
3
+ <div class="flex gap-4 px-6 overflow-auto">
4
+ <div class="flex w-[356px] flex-col gap-2">
5
+ <div v-for="(parent, sectionIndex) in modelValue.sections" class="border-b border-gray-200 pb-2">
6
+ <div
7
+ @click="openStates[sectionIndex] = !openStates[sectionIndex]"
8
+ class="flex cursor-pointer items-center justify-between px-2 py-4"
9
+ >
9
10
  <span class="text-lg font-semibold text-brand-800">
10
11
  {{ parent.title }}
11
12
  </span>
12
- <div class="relative z-10 flex items-center gap-3">
13
- <div class="cursor-pointer">
14
- <ChevronRight class="h-5 w-5" v-if="!openStates[sectionIndex]" />
15
- <ChevronDown class="h-5 w-5" v-else />
13
+ <div class="relative flex items-center gap-3">
14
+ <div class="cursor-pointer">
15
+ <ChevronRight class="h-5 w-5" v-if="!openStates[sectionIndex]" />
16
+ <ChevronDown class="h-5 w-5" v-else />
17
+ </div>
16
18
  </div>
17
19
  </div>
18
- </div>
19
- <div class="flex flex-col gap-2" v-if="openStates[sectionIndex]">
20
- <template v-for="(component, index) in parent.components">
21
- <div
22
- @click="selectComponent(sectionIndex, component, index)"
23
- class="flex cursor-pointer items-center justify-between rounded-lg py-1 pl-6 pr-2 hover:bg-gray-200"
24
- >
25
- <div class="flex flex-col">
26
- <div class="text-xs text-gray-600">Sub-module</div>
27
- <div class="text-sm font-medium text-gray-900">
28
- {{ component.name }}
20
+ <div class="flex flex-col gap-2" v-if="openStates[sectionIndex]">
21
+ <template v-for="(component, index) in parent.components">
22
+ <div
23
+ @click="selectComponent(sectionIndex, component, index)"
24
+ class="flex cursor-pointer items-center justify-between rounded-lg py-1.5 pl-6 pr-2 hover:bg-gray-100"
25
+ :class="{'bg-gray-200 hover:bg-gray-200': selected?.sectionIndex === sectionIndex && selected?.componentIndex === index}"
26
+ >
27
+ <div class="flex flex-col">
28
+ <div class="text-xs text-gray-600">Sub-module</div>
29
+ <div class="text-sm font-medium text-gray-900">
30
+ {{ component.name }}
31
+ </div>
32
+ </div>
33
+ <div class="flex items-center justify-between gap-3 px-[10px]">
34
+ <ChevronRight class="h-5 w-5" />
29
35
  </div>
30
36
  </div>
31
- <div class="flex items-center justify-between gap-3">
32
- <ChevronRight class="h-5 w-5" />
33
- </div>
34
- </div>
35
- </template>
37
+ </template>
38
+ </div>
36
39
  </div>
37
40
  </div>
41
+ <div class="flex h-full flex-1 flex-col rounded-xl bg-gray-50 px-6 py-5 mb-20">
42
+ <Instructions v-if="!selected" />
43
+ <component
44
+ :is="currentComponent"
45
+ :key="selected?.sectionIndex + selected?.componentIndex"
46
+ :data="selected"
47
+ :sites="sites"
48
+ @update="update"
49
+ ></component>
50
+ </div>
38
51
  </div>
39
- <div class="flex h-full flex-1 flex-col rounded-xl bg-gray-50 px-6 py-5">
40
- <Instructions v-if="!selected" />
41
- <component
42
- :is="currentComponent"
43
- :key="selected?.sectionIndex + selected?.componentIndex"
44
- :data="selected"
45
- :sites="sites"
46
- @update="update"
47
- ></component>
48
- </div>
52
+ <slot>
53
+ <div class="flex justify-between space-x-xsSpace pt-xsSpace gap-4 fixed bottom-0 w-full bg-gray-200 py-2 px-6">
54
+ <a @click="close"
55
+ class="w-[200px] py-[9px] bg-white rounded-full border border-gray-300 shadow-xs text-md font-semibold hover:bg-gray-100 text-gray-700 text-center cursor-pointer">
56
+ Cancel
57
+ </a>
58
+ <a @click.prevent="save"
59
+ class="w-full py-[9px] rounded-full shadow-xs text-md font-semibold text-white bg-brand-600 hover:bg-brand-700 border border-brand-600 text-center cursor-pointer"
60
+ >Save changes</a>
61
+ </div>
62
+ </slot>
49
63
  </div>
50
64
  </template>
51
65
  <script setup>
52
- import { ref, watch, markRaw, computed } from "vue";
66
+ import { ref, markRaw, computed } from "vue";
53
67
  import ChevronRight from "@/assets/img/icons/chevron-right.svg";
54
68
  import ChevronDown from "@/assets/img/icons/chevron-down.svg";
55
69
  import Instructions from "@/components/builders/Instructions.vue";
56
70
  import VSliders from "@/components/builders/VSliders.vue";
57
71
  import VLinks from "@/components/builders/VLinks.vue";
58
72
 
73
+ const emit = defineEmits(["save", "close"]);
59
74
  const props = defineProps({
60
- page: {
75
+ modelValue: {
61
76
  required: true,
62
77
  type: Object,
63
78
  },
@@ -79,9 +94,8 @@ const props = defineProps({
79
94
  });
80
95
 
81
96
  const openStates = ref(JSON.parse(window.localStorage.getItem("pageBuilderOpenStates")));
82
- const sections = ref(props.page?.sections ?? []);
97
+ const sections = ref(props.modelValue?.sections ?? []);
83
98
  let selected = ref(null);
84
- const contentUnchanged = ref(true);
85
99
  const componentMaps = ref({
86
100
  sliders: markRaw(VSliders),
87
101
  links: markRaw(VLinks),
@@ -99,18 +113,20 @@ if (!openStates.value) {
99
113
 
100
114
  const selectComponent = (sectionIndex, component, index) => {
101
115
  selected.value = {
102
- page: props.page,
116
+ page: props.modelValue,
103
117
  component: component,
104
118
  componentIndex: index,
105
119
  sectionIndex: sectionIndex,
106
120
  };
107
- console.log(selected, currentComponent);
108
121
  };
109
122
 
110
- setTimeout(() => {
111
- console.log(sections)
112
- selectComponent(0, sections.value[0].components[1], 0)
113
- })
123
+ const close = () => {
124
+ emit("close", props.modelValue);
125
+ };
126
+
127
+ const save = () => {
128
+ emit("save", props.modelValue);
129
+ };
114
130
 
115
131
  const currentComponent = computed(() => {
116
132
  if (!selected.value?.component?.type) {
@@ -119,25 +135,4 @@ const currentComponent = computed(() => {
119
135
 
120
136
  return componentMaps.value[selected.value?.component?.type];
121
137
  });
122
-
123
- const update = (data) => {
124
- // content.value[selected.value.sectionIndex].value.submodules[selected.value.itemIndex] = data;
125
-
126
- // axios
127
- // .post("api.admin.pages.update-content", {
128
- // attributes: content.value,
129
- // })
130
- // .then(() => {
131
- // contentUnchanged.value = true;
132
- // })
133
- // .catch((error) => {
134
- // console.error(error);
135
- // });
136
- };
137
-
138
- watch(
139
- () => sections.value,
140
- () => (contentUnchanged.value = false),
141
- { deep: true },
142
- );
143
138
  </script>
@@ -8,7 +8,7 @@
8
8
  </div>
9
9
  </template>
10
10
  <script setup>
11
- import {ref, watch, markRaw} from "vue";
11
+ import {ref, markRaw} from "vue";
12
12
  import HeroHeader from "@/components/presenters/modules/HeroHeader.vue";
13
13
 
14
14
  const props = defineProps({
@@ -106,15 +106,17 @@
106
106
  </card>
107
107
  </div>
108
108
  </div>
109
- <div class="flex justify-between space-x-xsSpace pt-xsSpace gap-4 sticky bottom-0 w-full bg-gray-200 py-2 px-6">
110
- <a @click="close"
111
- class="w-[200px] py-[9px] bg-white rounded-full border border-gray-300 shadow-xs text-md font-semibold hover:bg-gray-100 text-gray-700 text-center cursor-pointer">
112
- Cancel
113
- </a>
114
- <a @click.prevent="save"
115
- class="w-full py-[9px] rounded-full shadow-xs text-md font-semibold text-white bg-brand-600 hover:bg-brand-700 border border-brand-600 text-center cursor-pointer"
116
- >Save changes</a>
117
- </div>
109
+ <slot>
110
+ <div class="flex justify-between space-x-xsSpace pt-xsSpace gap-4 sticky bottom-0 w-full bg-gray-200 py-2 px-6">
111
+ <a @click="close"
112
+ class="w-[200px] py-[9px] bg-white rounded-full border border-gray-300 shadow-xs text-md font-semibold hover:bg-gray-100 text-gray-700 text-center cursor-pointer">
113
+ Cancel
114
+ </a>
115
+ <a @click.prevent="save"
116
+ class="w-full py-[9px] rounded-full shadow-xs text-md font-semibold text-white bg-brand-600 hover:bg-brand-700 border border-brand-600 text-center cursor-pointer"
117
+ >Save changes</a>
118
+ </div>
119
+ </slot>
118
120
  </div>
119
121
  </template>
120
122
  <script setup>
@@ -125,6 +127,7 @@ import LinkedTo from "@/components/common/LinkedTo.vue";
125
127
  import InputWrapper from "@/components/common/InputWrapper.vue";
126
128
  import VToggle from "@/components/common/VToggle.vue";
127
129
  import FileUpload from "@/components/common/FileUpload.vue";
130
+ import axios from "axios";
128
131
 
129
132
  const emit = defineEmits(["update"]);
130
133
 
@@ -133,6 +136,14 @@ const props = defineProps({
133
136
  type: Object,
134
137
  required: true,
135
138
  },
139
+ saveEndpoint: {
140
+ type: String,
141
+ required: false,
142
+ },
143
+ cancelEndpoint: {
144
+ type: String,
145
+ required: false,
146
+ },
136
147
  sites: {
137
148
  type: Object,
138
149
  required: true,
@@ -150,10 +161,22 @@ const form = ref({
150
161
 
151
162
  const save = () => {
152
163
  emit("update", form.value);
164
+
165
+ if (props.saveEndpoint) {
166
+ axios.post(props.saveEndpoint, form.value).then(() => {
167
+ if (props.cancelEndpoint) {
168
+ window.location.href = props.cancelEndpoint;
169
+ }
170
+ })
171
+ }
153
172
  };
154
173
 
155
174
  const close = () => {
156
175
  emit("close", form.value);
176
+
177
+ if (props.cancelEndpoint) {
178
+ window.location.href = props.cancelEndpoint;
179
+ }
157
180
  };
158
181
 
159
182
  const descriptionWordCount = computed(() => {
@@ -33,18 +33,7 @@
33
33
  Link #{{index + 1}}
34
34
  </div>
35
35
  <div class="relative flex items-end">
36
- <action-menu>
37
- <button
38
- class="flex min-w-[15rem] items-center gap-2 p-2.5 hover:rounded-md hover:bg-gray-100"
39
- type="button"
40
- @click="handleDeleteItem(index)"
41
- >
42
- <TrashIcon class="h-5 w-5 cursor-pointer stroke-gray-500"/>
43
- <span class="text-sm font-medium text-gray-700">
44
- Delete Link
45
- </span>
46
- </button>
47
- </action-menu>
36
+ <ActionMenu @removeItem="handleDeleteItem(index)"/>
48
37
  </div>
49
38
  </div>
50
39
  <div class="flex flex-col gap-6">
@@ -83,11 +72,9 @@
83
72
  <script setup>
84
73
  import {ref, nextTick} from "vue";
85
74
  import PlusIcon from "@/assets/img/icons/plus.svg";
86
- import TrashIcon from "@/assets/img/icons/trash-01.svg";
87
75
  import {defaultProps} from "@/components/helpers/defaultProps.js";
88
- import {createSlide} from "@/components/helpers/pageBuilderFactory";
76
+ import {createLink} from "@/components/helpers/pageBuilderFactory";
89
77
  import ActionMenu from "@/components/common/ActionMenu.vue";
90
- import VToggle from "@/components/common/VToggle.vue";
91
78
  import InputWrapper from "@/components/common/InputWrapper.vue";
92
79
  import LinkedTo from "@/components/common/LinkedTo.vue";
93
80
  import VModal from "@/components/common/VModal.vue";
@@ -107,12 +94,7 @@ function addItem() {
107
94
  if (componentData.value.data.length >= componentData.value.max_items) {
108
95
  return;
109
96
  }
110
- componentData.value.data.push({
111
- title: "",
112
- type: "site-content",
113
- url: "",
114
- openInNewTab: "",
115
- });
97
+ componentData.value.data.push(createLink());
116
98
 
117
99
  nextTick(() => {
118
100
  if (lastItemRef.value) {
@@ -1,26 +1,26 @@
1
1
  <template>
2
2
  <div class="flex justify-between pb-2">
3
3
  <p class="text-lg font-semibold text-gray-900">
4
- {{ data.component.name }}
4
+ {{ dataRef.name }}
5
5
  </p>
6
6
  </div>
7
7
  <div class="flex flex-col gap-4">
8
8
  <div class="text-gray-600 border-b border-gray-200 pb-4 text-sm">
9
- {{ props.data.component.supportive_text }}
9
+ {{ dataRef.supportive_text }}
10
10
  </div>
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
14
  <div class="font-semibold text-gray-900">Sliders</div>
15
- <div class="text-sm text-gray-600">This slider can contain up to {{ data.component.max_items }} slides</div>
15
+ <div class="text-sm text-gray-600">This slider can contain up to {{ dataRef.max_items }} slides</div>
16
16
  </div>
17
17
  <div>
18
18
  <button
19
- :disabled="dataRef.data.length >= data.component.max_items"
19
+ :disabled="dataRef.data.length >= dataRef.max_items"
20
20
  @click="addItem"
21
21
  type="button"
22
22
  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"
23
- :class="{ 'border-gray-100 bg-gray-100 !text-gray-400 hover:bg-gray-100': dataRef.data.length >= data.component.max_items }"
23
+ :class="{ 'border-gray-100 bg-gray-100 !text-gray-400 hover:bg-gray-100': dataRef.data.length >= dataRef.max_items }"
24
24
  >
25
25
  <PlusIcon class="h-5 w-5"></PlusIcon>
26
26
  <span>Add</span>
@@ -28,9 +28,11 @@
28
28
  </div>
29
29
  </div>
30
30
  <div class="flex flex-col gap-3">
31
- <div class="flex items-center gap-4 px-2 py-1 hover:bg-gray-100 rounded-lg" v-for="(item, index) in dataRef.data">
32
- <div class="flex flex-1 cursor-pointer items-center justify-between relative">
33
- <div class="flex flex-1 flex-col" @click="edit(index)">
31
+ <div v-for="(item, index) in dataRef.data"
32
+ class="flex items-center gap-4 px-2 py-1 hover:bg-gray-100 rounded-lg"
33
+ :class="{'bg-gray-200 hover:bg-gray-200': openSlideStates[index]}">
34
+ <div class="flex flex-1 cursor-pointer items-center justify-between relative" @click="toggleSlide(index)">
35
+ <div class="flex flex-1 flex-col">
34
36
  <div class="text-xs text-gray-600">
35
37
  Slider #{{ index + 1 }}
36
38
  </div>
@@ -38,7 +40,7 @@
38
40
  {{ item.title }}
39
41
  </div>
40
42
  </div>
41
- <ActionMenu @removeItem="handleDeleteItem(index)" />
43
+ <ActionMenu @removeItem="handleDeleteItem(index)" :enable-edit="true" @editItem="edit(item, index)"/>
42
44
  </div>
43
45
  </div>
44
46
  </div>
@@ -62,6 +64,26 @@ const props = defineProps({
62
64
  const dataRef = ref(props.data.component);
63
65
  const modalRef = ref(null);
64
66
 
67
+ const openSlideStates = ref(JSON.parse(window.localStorage.getItem("openSlideStates")));
68
+ if (!openSlideStates.value) {
69
+ openSlideStates.value = {};
70
+
71
+ dataRef.value.data.forEach((item, index) => {
72
+ openSlideStates.value[index] = false;
73
+ });
74
+
75
+ console.log(openSlideStates)
76
+ window.localStorage.setItem("openSlideStates", JSON.stringify(openSlideStates.value));
77
+ }
78
+
79
+ const toggleSlide = (index) => {
80
+ Object.keys(openSlideStates.value).forEach((key) => {
81
+ openSlideStates.value[key] = false;
82
+ });
83
+ openSlideStates.value[index] = !openSlideStates.value[index];
84
+ window.localStorage.setItem("openSlideStates", JSON.stringify(openSlideStates.value));
85
+ };
86
+
65
87
  const addItem = () => {
66
88
  dataRef.value.data.push(createSlide());
67
89
  emit("update", dataRef.value);
@@ -76,12 +98,11 @@ const deleteCallback = (index) => {
76
98
  emit("update", dataRef.value);
77
99
  };
78
100
 
79
- const edit = (index) => {
80
- // window.location.href = route("admin.pages.edit-attribute", {
81
- // page: props.data.pageAttribute.page_id,
82
- // attribute: props.data.pageAttribute.id,
83
- // moduleIndex: props.data.subModuleIndex,
84
- // itemIndex: index,
85
- // });
101
+ const edit = (item, index) => {
102
+ if (item.hasOwnProperty('edit_url')) {
103
+ window.location.href = item.edit_url;
104
+ }
105
+
106
+ window.location.href = `/admin/pages/${props?.data?.page?.id}/sections/${props?.data?.sectionIndex}/components/${props?.data?.componentIndex}/slides/${index}`;
86
107
  };
87
108
  </script>
@@ -2,7 +2,11 @@
2
2
  <div class="relative z-50" v-click-outside="click">
3
3
  <DotsHorizontal @click="open = !open" class="w-5 cursor-pointer text-gray-600" />
4
4
  <div class="absolute -left-60 top-6 w-[240px] rounded-lg border border-gray-200 bg-white" v-if="open">
5
- <a class="flex items-center gap-2 p-2.5 cursor-pointer h-[48px] w-full" @click="remove">
5
+ <a v-if="enableEdit" class="flex items-center gap-2 p-2.5 cursor-pointer h-[48px] w-full hover:bg-gray-100" @click="edit">
6
+ <Pencil class="w-5" />
7
+ Edit
8
+ </a>
9
+ <a class="flex items-center gap-2 p-2.5 cursor-pointer h-[48px] w-full hover:bg-gray-100" :class="{'hover:rounded-lg': !enableEdit}" @click="remove">
6
10
  <TrashIcon class="w-5" />
7
11
  Delete
8
12
  </a>
@@ -23,10 +27,18 @@ export default {
23
27
  <script setup>
24
28
  import DotsHorizontal from "@/assets/img/icons/dots-horizontal.svg";
25
29
  import TrashIcon from "@/assets/img/icons/trash-01.svg";
30
+ import Pencil from "@/assets/img/icons/pencil-01.svg";
26
31
  import { ref } from "vue";
27
32
  const open = ref(false);
28
33
  const emit = defineEmits(['removeItem']);
29
34
 
35
+ const props = defineProps({
36
+ enableEdit: {
37
+ type: Boolean,
38
+ default: false,
39
+ },
40
+ });
41
+
30
42
  const click = () => {
31
43
  if (!open.value) {
32
44
  return;
@@ -38,4 +50,9 @@ const remove = () => {
38
50
  emit('removeItem');
39
51
  open.value = false;
40
52
  }
53
+
54
+ const edit = () => {
55
+ emit('editItem');
56
+ open.value = false;
57
+ }
41
58
  </script>
@@ -1,7 +1,17 @@
1
1
  <template>
2
2
  <div class="file-upload">
3
3
  <input type="hidden" :name="name" :value="valueJson"/>
4
- <div v-if="file.length" class="preview">
4
+ <div v-if="modelValue" class="preview">
5
+ @todo handle upload here
6
+ <span class="file-upload-preview">
7
+ <img
8
+ class="img rounded-lg"
9
+ :src="modelValue"
10
+ title="Image"
11
+ />
12
+ </span>
13
+ </div>
14
+ <div v-else-if="file && file.length" class="preview">
5
15
  <span class="file-upload-preview">
6
16
  <img
7
17
  class="img"
@@ -17,7 +27,7 @@
17
27
  <i class="fal fa-times" @click="deleteFile(file)"></i>
18
28
  </a>
19
29
  </div>
20
- <div class="relative">
30
+ <div class="relative" v-show="!modelValue">
21
31
  <div class="dropzone border border-dashed rounded-lg z-10 h-[200px] w-full cursor-pointer relative"
22
32
  ref="dropzone">
23
33
  </div>
@@ -47,7 +57,7 @@ Dropzone.autoDiscover = false;
47
57
  const props = defineProps({
48
58
  name: {
49
59
  type: String,
50
- required: true,
60
+ required: false,
51
61
  },
52
62
  modelValue: {
53
63
  type: [String, Object],
@@ -55,11 +65,11 @@ const props = defineProps({
55
65
  },
56
66
  submitEndpoint: {
57
67
  type: String,
58
- required: true,
68
+ required: false,
59
69
  },
60
70
  deleteEndpoint: {
61
71
  type: String,
62
- required: true,
72
+ required: false,
63
73
  },
64
74
  formData: {
65
75
  type: Object,
@@ -67,7 +77,7 @@ const props = defineProps({
67
77
  },
68
78
  csrf: {
69
79
  type: String,
70
- required: true,
80
+ required: false,
71
81
  },
72
82
  });
73
83
  const emit = defineEmits(["update:modelValue"]);
@@ -17,25 +17,43 @@
17
17
  </span>
18
18
  <slot />
19
19
  <span class="absolute right-0 -bottom-6 text-sm text-gray-600 font-normal" v-if="limit">
20
- {{value.length}}/{{limit}} characters
20
+ {{ value?.length }}/{{ limit }} characters
21
21
  </span>
22
22
  <span class="absolute left-0 -bottom-6 text-sm text-gray-600 font-normal" v-if="showCount">
23
- Word count: {{value}}
23
+ Word count: {{ value }}
24
24
  </span>
25
25
  </label>
26
26
  </template>
27
27
 
28
- <script setup lang="ts">
29
-
30
- interface Props {
31
- field: string;
32
- labelText?: string;
33
- value?: any;
34
- limit?: number;
35
- showCount?: boolean;
36
- darkTheme?: boolean;
37
- isRequired?: boolean;
38
- }
39
-
40
- const props = defineProps<Props>();
28
+ <script setup>
29
+ const props = defineProps({
30
+ field: {
31
+ type: String,
32
+ required: true,
33
+ },
34
+ labelText: {
35
+ type: String,
36
+ default: "",
37
+ },
38
+ value: {
39
+ type: [String, Number],
40
+ default: "",
41
+ },
42
+ limit: {
43
+ type: Number,
44
+ default: null,
45
+ },
46
+ showCount: {
47
+ type: Boolean,
48
+ default: false,
49
+ },
50
+ darkTheme: {
51
+ type: Boolean,
52
+ default: false,
53
+ },
54
+ isRequired: {
55
+ type: Boolean,
56
+ default: false,
57
+ },
58
+ });
41
59
  </script>
@@ -45,10 +45,8 @@ export default {
45
45
  onMounted(() => {
46
46
  quillInstance = new Quill(editorContainer.value, props.options);
47
47
 
48
- // Set initial content
49
48
  quillInstance.root.innerHTML = props.modelValue;
50
49
 
51
- // Listen for text changes
52
50
  quillInstance.on("text-change", () => {
53
51
  emit("update:modelValue", quillInstance.root.innerHTML);
54
52
  });