@mixd-id/web-scaffold 0.1.230406087 → 0.1.230406089

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/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@mixd-id/web-scaffold",
3
3
  "private": false,
4
- "version": "0.1.230406087",
4
+ "version": "0.1.230406089",
5
5
  "scripts": {
6
6
  "dev": "vite serve",
7
7
  "build": "vite build",
@@ -22,14 +22,14 @@
22
22
  </div>
23
23
 
24
24
  <div class="mt-4" v-if="mode === 'alert'">
25
- <Button @click="$emit('dismiss')" class="min-w-[88px]">
25
+ <Button ref="btnOK" @click="$emit('dismiss')" class="min-w-[88px]">
26
26
  <strong class="px-4">
27
27
  {{ text }}
28
28
  </strong>
29
29
  </Button>
30
30
  </div>
31
31
  <div class="mt-4 flex flex-row gap-2" v-else-if="mode === 'confirm'">
32
- <Button @click="$emit('confirm')" class="min-w-[88px]">
32
+ <Button ref="btnOK" @click="$emit('confirm')" class="min-w-[88px]" tabindex="0">
33
33
  <strong class="px-4">
34
34
  {{ text }}
35
35
  </strong>
@@ -123,6 +123,7 @@ export default{
123
123
  methods: {
124
124
 
125
125
  onAfterAppear(){
126
+ this.$refs.btnOK.focus()
126
127
  this.$emit('appear')
127
128
  }
128
129
 
@@ -136,6 +136,10 @@ export default{
136
136
  outline: solid 1px rgb(var(--primary-700));
137
137
  border: solid 1px rgb(var(--primary-400));
138
138
  }
139
+ .button-primary:focus{
140
+ outline-color: rgb(var(--primary-800));
141
+ border-color: rgb(var(--primary-600));
142
+ }
139
143
  .button-primary:hover{
140
144
  @apply bg-primary-600;
141
145
  outline-color: rgb(var(--primary-800))
@@ -3,7 +3,11 @@
3
3
 
4
4
  <div ref="inner" :class="innerClass">
5
5
  <div v-for="item in items" :class="itemClass">
6
- <component :is="item.type" :="item.props" :ratio="ratio" />
6
+ <component :is="item.type"
7
+ :uid="item.uid"
8
+ :="item.props"
9
+ :ratio="ratio"
10
+ :ext-class="item.extClass" />
7
11
  </div>
8
12
  </div>
9
13
 
@@ -0,0 +1,59 @@
1
+ <template>
2
+ <Modal :state="isOpen" width="480" height="480">
3
+ <template v-slot:head>
4
+ <div class="relative p-5">
5
+ <h3>Pilih Kota</h3>
6
+ <div class="absolute top-0 right-0">
7
+ <button type="button" class="p-2" @click="close">
8
+ <svg width="24" height="24" viewBox="0 0 24 24" class="fill-text-300" xmlns="http://www.w3.org/2000/svg">
9
+ <path d="M6.53034 5.46965C6.23745 5.17676 5.76257 5.17676 5.46968 5.46965C5.17679 5.76255 5.17679 6.23742 5.46968 6.53031L10.9393 12L5.46967 17.4697C5.17678 17.7626 5.17678 18.2374 5.46967 18.5303C5.76256 18.8232 6.23744 18.8232 6.53033 18.5303L12 13.0606L17.4697 18.5303C17.7626 18.8232 18.2375 18.8232 18.5303 18.5303C18.8232 18.2374 18.8232 17.7626 18.5303 17.4697L13.0607 12L18.5303 6.53032C18.8232 6.23743 18.8232 5.76256 18.5303 5.46966C18.2374 5.17677 17.7626 5.17677 17.4697 5.46966L12 10.9393L6.53034 5.46965Z"/>
10
+ </svg>
11
+ </button>
12
+ </div>
13
+ </div>
14
+ </template>
15
+ <template v-slot:foot>
16
+ <div class="p-5">
17
+
18
+ </div>
19
+ </template>
20
+ <div class="flex-1 p-5">
21
+ </div>
22
+ </Modal>
23
+ </template>
24
+
25
+ <script>
26
+
27
+ export default{
28
+
29
+ inject: [ 'socketEmit2' ],
30
+
31
+ data(){
32
+ return {
33
+ isOpen: false
34
+ }
35
+ },
36
+
37
+ methods: {
38
+
39
+ open(id){
40
+ this.isOpen = true
41
+ },
42
+
43
+ close(){
44
+ this.isOpen = false
45
+ }
46
+
47
+ }
48
+
49
+ }
50
+
51
+ </script>
52
+
53
+ <style module>
54
+
55
+ .comp{
56
+
57
+ }
58
+
59
+ </style>
@@ -1,23 +1,23 @@
1
1
  <template>
2
2
 
3
- <div :class="compClass">
4
-
5
- <div v-if="mode === '' || mode === 'simple'">
6
- <select v-model="DD">
7
- <option disabled selected>Tanggal</option>
8
- <option v-for="d in 31" :value="d.toString().padStart(2, '0')">{{ d }}</option>
9
- </select>
10
- <select v-model="MM">
11
- <option disabled selected>Bulan</option>
12
- <option v-for="mmm in months" :value="mmm[0]">{{ mmm[1] }}</option>
13
- </select>
14
- <select v-model="YYYY">
15
- <option disabled selected>Tahun</option>
16
- <option v-for="yyyy in years" :value="yyyy">{{ yyyy }}</option>
17
- </select>
18
- </div>
19
-
20
- <div v-else-if="mode === 'popup'" ref="popup"
3
+ <div v-if="mode === ''" :class="compClass">
4
+ <select v-model="DD">
5
+ <option disabled selected>Tanggal</option>
6
+ <option v-for="d in 31" :value="d.toString().padStart(2, '0')">{{ d }}</option>
7
+ </select>
8
+ <select v-model="MM">
9
+ <option disabled selected>Bulan</option>
10
+ <option v-for="mmm in months" :value="mmm[0]">{{ mmm[1] }}</option>
11
+ </select>
12
+ <select v-model="YYYY">
13
+ <option disabled selected>Tahun</option>
14
+ <option v-for="yyyy in years" :value="yyyy">{{ yyyy }}</option>
15
+ </select>
16
+ </div>
17
+
18
+ <div v-else :class="compClass">
19
+
20
+ <div v-if="mode === 'popup'" ref="popup"
21
21
  @click="!readonly ? contextMenu = { caller:$refs.popup, value:this.modelValue } : null"
22
22
  class="flex-1">
23
23
  <input class="flex-1" type="text" readonly :value="DMMMYYYY"/>
@@ -102,9 +102,9 @@ export default{
102
102
 
103
103
  setup(props, { emit }){
104
104
 
105
- const DD = ref(dayjs(props.modelValue).format('DD'))
106
- const MM = ref(dayjs(props.modelValue).format('MM'))
107
- const YYYY = ref(dayjs(props.modelValue).format('YYYY'))
105
+ const DD = ref(dayjs(props.modelValue ?? props.defaultValue).format('DD'))
106
+ const MM = ref(dayjs(props.modelValue ?? props.defaultValue).format('MM'))
107
+ const YYYY = ref(dayjs(props.modelValue ?? props.defaultValue).format('YYYY'))
108
108
 
109
109
  watch([ YYYY, MM, DD ], (to) => {
110
110
  emit('update:modelValue', to.join('-'))
@@ -136,6 +136,8 @@ export default{
136
136
 
137
137
  modelValue:String,
138
138
 
139
+ defaultValue: String,
140
+
139
141
  readonly: undefined,
140
142
 
141
143
  },
@@ -262,9 +264,8 @@ export default{
262
264
  <style module>
263
265
 
264
266
  .datepicker {
265
- @apply h-[var(--h-cp)];
267
+ @apply min-h-[var(--h-cp)];
266
268
  @apply flex items-center rounded-lg overflow-hidden cursor-pointer relative;
267
- @apply border-[1px] border-text-200 bg-base-50;
268
269
  @apply cursor-pointer;
269
270
  }
270
271
  .datepicker:not(.readonly){
@@ -301,16 +302,34 @@ export default{
301
302
  @apply block fill-text-300;
302
303
  }
303
304
 
304
- .datepicker.mode-calendar{
305
+ .selected{
306
+ @apply bg-primary;
307
+ }
308
+
309
+ .mode-calendar{
305
310
  @apply p-2 h-auto;
306
311
  }
307
312
 
313
+ .mode-{
314
+ @apply flex flex-row gap-2;
315
+ }
316
+ .mode- select{
317
+ @apply bg-base border-[1px] border-text-200 rounded-lg;
318
+ @apply min-h-[var(--h-cp)];
319
+ }
320
+ .mode- select:first-child{
321
+ @apply w-12;
322
+ }
323
+ .mode- select:nth-child(2){
324
+ @apply w-72;
325
+ }
326
+ .mode- select:last-child{
327
+ @apply w-48;
328
+ }
329
+
308
330
  .datepicker-date--1, .datepicker-date-1{
309
331
  @apply text-gray-500;
310
332
  }
311
333
 
312
- .selected{
313
- @apply bg-primary;
314
- }
315
334
 
316
335
  </style>
@@ -3,7 +3,7 @@
3
3
  <slot name="start"></slot>
4
4
  <input :type="computedType" :disabled="isDisabled" @focus="isActive = true" @input="onInput" @blur="onBlur"
5
5
  :placeholder="placeholder" :maxlength="maxlength" ref="input" autocomplete="new-password"
6
- :value="displayedValue" :readonly="Boolean(readonly)"
6
+ :value="displayedValue" :readonly="Boolean(readonly)" @paste="onPaste"
7
7
  @keydown="onKeyDown"/>
8
8
  <button class="mr-2" v-if="Boolean(clearable) && !Boolean(readonly) && state >= 1 && modelValue" type="button" @click="$emit('clear')">
9
9
  <svg :class="$style.svg" width="19" height="19" viewBox="0 0 24 24" class="fill-text-200" xmlns="http://www.w3.org/2000/svg">
@@ -128,12 +128,31 @@ export default{
128
128
  this.$emit('blur', e)
129
129
  },
130
130
 
131
+ onPaste(e){
132
+ e.preventDefault()
133
+
134
+ let text = (e.clipboardData || window.clipboardData).getData("text");
135
+
136
+ switch(this.type){
137
+ case 'tel':
138
+ text = text.replace(/[^0-9]/g, '')
139
+ break
140
+ }
141
+
142
+ if(parseInt(this.maxlength) > 0){
143
+ text = text.substring(0, parseInt(this.maxlength))
144
+ }
145
+
146
+ this.$emit('update:modelValue', text)
147
+ },
148
+
131
149
  onKeyDown(e){
132
150
  if([ 'number', 'tel' ].includes(this.type)){
133
151
  if(((e.keyCode >= 48 && e.keyCode <= 57) || (e.keyCode >= 96 && e.keyCode <= 105) ||
134
152
  [ 8, 9, 13, 27, 37, 38, 39, 40, 46 ].includes(e.keyCode)) && !e.ctrlKey && !e.metaKey && !e.shiftKey && !e.altKey);
135
153
  else if(e.keyCode === 65 && (e.ctrlKey || e.metaKey));
136
154
  else if(e.keyCode === 82 && (e.ctrlKey || e.metaKey));
155
+ else if(e.keyCode === 86 && (e.ctrlKey || e.metaKey)); // ctrl-v
137
156
  else{
138
157
  e.preventDefault()
139
158
  }
package/src/index.js CHANGED
@@ -169,6 +169,14 @@ const util = {
169
169
  }
170
170
  },
171
171
 
172
+ removeEmpty(array, emptyValues = [ '', '{}', '[]' ]){
173
+ for (let i = array.length - 1; i >= 0; i--) {
174
+ if (!array[i] || emptyValues.includes(JSON.stringify(array[i])) || Object.values(array[i]).join('').length <= 0){
175
+ array.splice(i, 1);
176
+ }
177
+ }
178
+ },
179
+
172
180
  onEvent: (arr, updates, opt = {}) => {
173
181
 
174
182
  const [ model, event, items ] = updates
@@ -252,6 +260,7 @@ export default{
252
260
  app.component('Block', defineAsyncComponent(() => import("./components/Block.vue")))
253
261
  app.component('Button', defineAsyncComponent(() => import("./components/Button.vue")))
254
262
  app.component('Box', defineAsyncComponent(() => import("./components/Box.vue")))
263
+ app.component('CitySelector', defineAsyncComponent(() => import("./components/CitySelector.vue")))
255
264
  app.component('ColorPicker', defineAsyncComponent(() => import("./components/ColorPicker.vue")))
256
265
  app.component('SearchButton', defineAsyncComponent(() => import("./components/SearchButton.vue")))
257
266
  app.component('ChatTyping', defineAsyncComponent(() => import("./components/ChatTyping.vue")))
@@ -5,33 +5,6 @@
5
5
  v-show="viewType === _.value">
6
6
  <div class="grid grid-cols-2 gap-4">
7
7
 
8
- <div class="col-span-2">
9
- <div class="flex flex-row gap-2">
10
- <label class="flex-1 text-text-400">Items</label>
11
- <button type="button" class="text-primary"
12
- @click="$refs.imageModal.open({ viewType, item:{ type:'Image', props:{ src:[], margin:[], padding:[], bdColor:[], bdSize:[ '0' ], bdRadius:[], bdStyle:[ 'solid' ] } } })">Add Item</button>
13
- </div>
14
- <ListItem :items="item.props.items" class="mt-1"
15
- @reorder="(from, to) => { item.props.items.splice(to, 0, item.props.items.splice(from, 1)[0]); $emit('change') }">
16
- <template v-slot="{ item:comp, index }">
17
- <div class="flex flex-row gap-3 p-2 bg-base-500 items-center rounded-lg mb-2">
18
- <div data-reorder>
19
- <svg width="14" height="14" class="fill-text-300" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 512 512"><path d="M496 288H16c-8.8 0-16 7.2-16 16v32c0 8.8 7.2 16 16 16h480c8.8 0 16-7.2 16-16v-32c0-8.8-7.2-16-16-16zm0-128H16c-8.8 0-16 7.2-16 16v32c0 8.8 7.2 16 16 16h480c8.8 0 16-7.2 16-16v-32c0-8.8-7.2-16-16-16z"/></svg>
20
- </div>
21
- <div class="flex-1"
22
- @click="$refs.imageModal.open({ item:comp, index, viewType })">
23
- <component :is="`${comp.type}Item`" :item="comp" :viewType="viewType" :viewTypes="viewTypes" />
24
- </div>
25
- <div>
26
- <button type="button" @click="confirm('Remove item?', { onConfirm: () => { item.props.items.splice(index, 1); $emit('change') }})">
27
- <svg width="16" height="16" class="fill-text-300 hover:fill-red-500" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 320 512"><path d="M207.6 256l107.72-107.72c6.23-6.23 6.23-16.34 0-22.58l-25.03-25.03c-6.23-6.23-16.34-6.23-22.58 0L160 208.4 52.28 100.68c-6.23-6.23-16.34-6.23-22.58 0L4.68 125.7c-6.23 6.23-6.23 16.34 0 22.58L112.4 256 4.68 363.72c-6.23 6.23-6.23 16.34 0 22.58l25.03 25.03c6.23 6.23 16.34 6.23 22.58 0L160 303.6l107.72 107.72c6.23 6.23 16.34 6.23 22.58 0l25.03-25.03c6.23-6.23 6.23-16.34 0-22.58L207.6 256z"/></svg>
28
- </button>
29
- </div>
30
- </div>
31
- </template>
32
- </ListItem>
33
- </div>
34
-
35
8
  <div>
36
9
  <label class="text-text-400">Ratio</label>
37
10
  <Dropdown v-model="item.props.ratio[idx]" class="mt-1" @change="$emit('change')">
@@ -105,8 +78,8 @@
105
78
  <template #foot="{ context }">
106
79
  <div class="p-5">
107
80
  <Button type="button" class="w-full" @click="context.index >= 0 ?
108
- item.props.items[context.index] = context.item :
109
- item.props.items.push(context.item);
81
+ item.items[context.index] = context.item :
82
+ item.items.push(context.item);
110
83
  $refs.imageModal.close();
111
84
  $emit('change')
112
85
  ">
@@ -154,6 +127,12 @@ export default{
154
127
 
155
128
  imageRatio(){
156
129
  return 'aspect-[4/1] md:aspect-[8/1]'
130
+ },
131
+
132
+ items(){
133
+ if(!Array.isArray(this.item.items))
134
+ this.item.items = []
135
+ return this.item.items
157
136
  }
158
137
 
159
138
  }
@@ -1,28 +1,72 @@
1
1
  <template>
2
- <div :class="$style.comp">
3
- <div :class="$style.form">
2
+ <div :class="compClass">
3
+ <div>
4
+ <h2 v-if="title">{{ title }}</h2>
5
+ <p v-if="description" v-html="description"></p>
6
+ </div>
4
7
 
5
- <div>
6
- <h2 v-if="title">{{ title }}</h2>
7
- <p v-if="description" v-html="description"></p>
8
- </div>
8
+ <div class="flex flex-col gap-4">
9
+ <div v-for="field in fields">
10
+ <label class="text-gray-600">{{ field.label ?? field.type }}</label>
9
11
 
10
- <div class="flex flex-col gap-4">
11
- <div v-for="field in fields">
12
- <label class="text-gray-700">{{ field.label ?? field.type }}</label>
12
+ <div class="mt-1">
13
13
  <Textbox v-if="field.type === 'name'" v-model="form[field.type]" class="bg-white text-xl" :readonly="completed" />
14
- <Textbox v-if="field.type === 'email'" v-model="form[field.type]" class="bg-white text-xl md:w-[300px] max-w-full" :readonly="completed" />
15
- <Textbox v-else-if="field.type === 'mobileNumber'" v-model="form[field.type]" class="bg-white text-xl" type="tel" :readonly="completed" />
16
- <Textbox v-else-if="field.type === 'referralCode'" v-model="form[field.type]" class="bg-white text-xl md:w-[200px] max-w-full" :readonly="completed" />
14
+
15
+ <Textbox v-else-if="field.type === 'university'" v-model="form[field.type]"
16
+ class="bg-white text-xl" :readonly="completed" />
17
+
18
+ <Textbox v-else-if="field.type === 'ktpNumber'" maxlength="16" type="tel" v-model="form[field.type]"
19
+ class="bg-white text-xl font-mono" :readonly="completed" />
20
+
21
+ <Textbox v-else-if="field.type === 'email'" v-model="form[field.type]"
22
+ class="bg-white text-xl md:w-[300px] max-w-full" :readonly="completed" />
23
+
24
+ <Textbox v-else-if="field.type === 'mobileNumber'" v-model="form[field.type]"
25
+ class="bg-white text-xl font-mono" maxlength="18" type="tel" :readonly="completed" />
26
+
27
+ <Textbox v-else-if="field.type === 'referralCode'" v-model="form[field.type]"
28
+ class="bg-white text-xl md:w-[200px] max-w-full" :readonly="completed" />
29
+
30
+ <div v-else-if="field.type === 'birthDate'" class="flex flex-col md:flex-row gap-2">
31
+ <Textbox v-model="form['birthPlace']" class="bg-base cursor-pointer" @click="$refs.citySelector.open()" readonly="true"
32
+ placeholder="Kota">
33
+ <template #end>
34
+ <div class="p-3">
35
+ <svg xmlns="http://www.w3.org/2000/svg" width="14" height="14" class="fill-text-200" viewBox="0 0 320 512"><path d="M287.968 160H32.038c-28.425 0-42.767 34.488-22.627 54.627l127.962 128c12.496 12.496 32.758 12.497 45.255 0l127.968-128C330.695 194.528 316.45 160 287.968 160zM160 320L32 192h256L160 320z"/></svg>
36
+ </div>
37
+ </template>
38
+ </Textbox>
39
+ <Datepicker v-model="form['birthDate']" class="w-[200px] text-lg" default-value="2000-01-01" :readonly="completed" />
40
+ </div>
41
+
42
+ <Dropdown v-else-if="field.type === 'position'" v-model="form[field.type]" class="text-xl">
43
+ <option v-for="item in field.items" :value="item.text">{{ item.text }}</option>
44
+ </Dropdown>
45
+
46
+ <Dropdown v-else-if="field.type === 'provinceName'" v-model="form[field.type]" class="text-xl">
47
+ <option v-for="item in field.items" :value="item.text">{{ item.text }}</option>
48
+ </Dropdown>
49
+
50
+ <Dropdown v-else-if="field.type === 'education'" v-model="form[field.type]" class="text-xl">
51
+ <option v-for="item in field.items" :value="item.text">{{ item.text }}</option>
52
+ </Dropdown>
53
+
54
+ <Dropdown v-else-if="field.type === 'major'" v-model="form[field.type]" class="text-xl">
55
+ <option v-for="item in field.items" :value="item.text">{{ item.text }}</option>
56
+ </Dropdown>
57
+
17
58
  <Textarea v-else-if="field.type === 'remark'" v-model="form[field.type]" rows="2" :readonly="completed"></Textarea>
59
+
60
+ <pre v-else>{{ field.type }}</pre>
18
61
  </div>
19
62
  </div>
63
+ </div>
20
64
 
21
- <Button ref="submitBtn" class="w-full" @click="submitForm" :state="!canSubmit ? -1 : 1">
22
- <h5>{{ completed ? 'Form berhasil dikirim' : 'Kirim' }}</h5>
23
- </Button>
65
+ <CitySelector ref="citySelector" @change="(city) => form['birthPlace'] = city.alias"/>
24
66
 
25
- </div>
67
+ <Button ref="submitBtn" class="w-full" @click="submitForm" :state="!canSubmit ? -1 : 1">
68
+ <h5>{{ completed ? 'Form berhasil dikirim' : 'Kirim' }}</h5>
69
+ </Button>
26
70
  </div>
27
71
  </template>
28
72
 
@@ -49,6 +93,7 @@ export default{
49
93
  padding: Array,
50
94
  enabled: undefined,
51
95
  name: String,
96
+ class: String,
52
97
 
53
98
  title: String,
54
99
  description: String,
@@ -68,8 +113,12 @@ export default{
68
113
  this.$refs.submitBtn.setState(2)
69
114
  axios({
70
115
  method: this.submitMethod ?? 'post',
71
- url: this.submitUrl,
72
- data: this.form,
116
+ url: import.meta.env.VITE_API_HOST + this.submitUrl,
117
+ data: {
118
+ url: this.$route.path,
119
+ componentUid: this.uid,
120
+ ...this.form
121
+ },
73
122
  params: this.form
74
123
  })
75
124
  .then(_ => this.completed = true)
@@ -82,15 +131,15 @@ export default{
82
131
  computed: {
83
132
 
84
133
  canSubmit(){
85
- return this.fields.every(field => [ 'referralCode', 'email', 'remark' ].includes(field.type) || this.form[field.type]) &&
134
+ return this.fields.length > 0 &&
135
+ this.fields.every(field => !field.required || this.form[field.type]) &&
136
+ Object.values(this.form).join('').length > 0 &&
86
137
  !this.completed
87
138
  },
88
139
 
89
140
  compClass(){
90
141
  return [
91
142
  this.$style.comp,
92
- this.status === 1 && this.spinnerType === 'shimmer' && !this.$slots['loading'] ?
93
- this.$style.shimmer : '',
94
143
  this.extClass,
95
144
  this.class ?? ''
96
145
  ]
@@ -120,16 +169,11 @@ export default{
120
169
  <style module>
121
170
 
122
171
  .comp{
123
- @apply flex-1;
172
+ @apply flex-1 w-full max-w-[480px] mx-auto rounded-lg overflow-hidden border-text-200 border-[1px] p-6;
173
+ @apply flex flex-col gap-8 bg-base;
124
174
  background-image: v-bind(bgImages[0]);
125
175
  }
126
176
 
127
- .form{
128
- @apply mx-auto bg-gray-100 border-text-200 border-[1px] p-6 rounded-lg;
129
- @apply w-full max-w-[420px];
130
- @apply flex flex-col gap-8;
131
- }
132
-
133
177
  @media screen and (min-width: 768px){
134
178
  .comp{
135
179
  background-image: v-bind(bgImages[1]);
@@ -17,16 +17,21 @@
17
17
  Add Field
18
18
  </button>
19
19
  </div>
20
- <ListItem :items="item.props.fields" class="mt-2" bodyClass="flex flex-col gap-1"
20
+ <ListItem :items="item.props.fields" class="mt-2 border-text-200 border-[1px] rounded-lg overflow-hidden"
21
+ bodyClass="flex flex-col gap-1 divide-y divide-text-200"
21
22
  @reorder="(from, to) => { item.props.fields.splice(to, 0, item.props.fields.splice(from, 1)[0]); $emit('change') }">
22
23
  <template v-slot="{ item:field, index }">
23
- <div class="flex flex-row items-center gap-2 p-3 bg-base-500 rounded-lg">
24
+ <div class="flex flex-row items-center gap-2 px-3 bg-base-500">
24
25
  <svg data-reorder width="14" height="14" class="fill-text-300" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 512 512"><path d="M496 288H16c-8.8 0-16 7.2-16 16v32c0 8.8 7.2 16 16 16h480c8.8 0 16-7.2 16-16v-32c0-8.8-7.2-16-16-16zm0-128H16c-8.8 0-16 7.2-16 16v32c0 8.8 7.2 16 16 16h480c8.8 0 16-7.2 16-16v-32c0-8.8-7.2-16-16-16z"/></svg>
25
- <div class="flex-1 px-2">
26
- <label>{{ field.type }}</label>
26
+ <div>
27
+ <Checkbox v-model="field.required" @change="$emit('change')"/>
27
28
  </div>
28
- <button type="button" @click="confirm($t('Remove this item?'), '', () => { item.props.fields.splice(index, 1); $emit('change') })">
29
- <svg width="16" height="16" class="fill-text-300 hover:fill-red-500" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 320 512"><path d="M207.6 256l107.72-107.72c6.23-6.23 6.23-16.34 0-22.58l-25.03-25.03c-6.23-6.23-16.34-6.23-22.58 0L160 208.4 52.28 100.68c-6.23-6.23-16.34-6.23-22.58 0L4.68 125.7c-6.23 6.23-6.23 16.34 0 22.58L112.4 256 4.68 363.72c-6.23 6.23-6.23 16.34 0 22.58l25.03 25.03c6.23 6.23 16.34 6.23 22.58 0L160 303.6l107.72 107.72c6.23 6.23 16.34 6.23 22.58 0l25.03-25.03c6.23-6.23 6.23-16.34 0-22.58L207.6 256z"/></svg>
29
+ <div class="flex-1 px-2 text-ellipsis overflow-hidden whitespace-nowrap">
30
+ <label>{{ field.label ?? field.type }}</label>
31
+ </div>
32
+ <button class="text-sm text-primary" v-if="field._edit" @click="$refs[field._edit].open(field)">Edit</button>
33
+ <button type="button" @click="confirm($t('Remove this field?'), '', () => { item.props.fields.splice(index, 1); $emit('change') })">
34
+ <svg width="14" height="14" class="fill-text-300 hover:fill-red-500" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 320 512"><path d="M207.6 256l107.72-107.72c6.23-6.23 6.23-16.34 0-22.58l-25.03-25.03c-6.23-6.23-16.34-6.23-22.58 0L160 208.4 52.28 100.68c-6.23-6.23-16.34-6.23-22.58 0L4.68 125.7c-6.23 6.23-6.23 16.34 0 22.58L112.4 256 4.68 363.72c-6.23 6.23-6.23 16.34 0 22.58l25.03 25.03c6.23 6.23 16.34 6.23 22.58 0L160 303.6l107.72 107.72c6.23 6.23 16.34 6.23 22.58 0l25.03-25.03c6.23-6.23 6.23-16.34 0-22.58L207.6 256z"/></svg>
30
35
  </button>
31
36
  </div>
32
37
  </template>
@@ -49,10 +54,10 @@
49
54
  <div class="flex-1 flex flex-col gap-2">
50
55
  <label class="text-text-400 leading-1">Send whatsapp to</label>
51
56
  <div class="flex flex-row gap-2">
52
- <Textbox class="flex-1" v-model="action.number" type="tel" />
57
+ <Textbox class="flex-1" v-model="action.number" type="tel"/>
53
58
  <Button type="button" class="text-primary px-3"
54
59
  :state="!action.number ? -1 : 1"
55
- @click="delete action._edit">OK</Button>
60
+ @click="delete action._edit;$emit('change')">OK</Button>
56
61
  </div>
57
62
  </div>
58
63
  </div>
@@ -64,7 +69,7 @@
64
69
  <button type="button" @click="action._edit = true">
65
70
  <svg width="13" height="13" class="fill-text-300 hover:fill-primary-500" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 576 512"><path d="M402.6 83.2l90.2 90.2c3.8 3.8 3.8 10 0 13.8L274.4 405.6l-92.8 10.3c-12.4 1.4-22.9-9.1-21.5-21.5l10.3-92.8L388.8 83.2c3.8-3.8 10-3.8 13.8 0zm162-22.9l-48.8-48.8c-15.2-15.2-39.9-15.2-55.2 0l-35.4 35.4c-3.8 3.8-3.8 10 0 13.8l90.2 90.2c3.8 3.8 10 3.8 13.8 0l35.4-35.4c15.2-15.3 15.2-40 0-55.2zM384 346.2V448H64V128h229.8c3.2 0 6.2-1.3 8.5-3.5l40-40c7.6-7.6 2.2-20.5-8.5-20.5H48C21.5 64 0 85.5 0 112v352c0 26.5 21.5 48 48 48h352c26.5 0 48-21.5 48-48V306.2c0-10.7-12.9-16-20.5-8.5l-40 40c-2.2 2.3-3.5 5.3-3.5 8.5z"/></svg>
66
71
  </button>
67
- <button type="button" @click="confirm($t('Remove this item?'), '', () => { item.props.onSubmit.splice(index, 1); $emit('change') })">
72
+ <button type="button" @click="confirm($t('Remove this action?'), '', () => { item.props.onSubmit.splice(index, 1); $emit('change') })">
68
73
  <svg width="16" height="16" class="fill-text-300 hover:fill-red-500" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 320 512"><path d="M207.6 256l107.72-107.72c6.23-6.23 6.23-16.34 0-22.58l-25.03-25.03c-6.23-6.23-16.34-6.23-22.58 0L160 208.4 52.28 100.68c-6.23-6.23-16.34-6.23-22.58 0L4.68 125.7c-6.23 6.23-6.23 16.34 0 22.58L112.4 256 4.68 363.72c-6.23 6.23-6.23 16.34 0 22.58l25.03 25.03c6.23 6.23 16.34 6.23 22.58 0L160 303.6l107.72 107.72c6.23 6.23 16.34 6.23 22.58 0l25.03-25.03c6.23-6.23 6.23-16.34 0-22.58L207.6 256z"/></svg>
69
74
  </button>
70
75
  </div>
@@ -106,27 +111,13 @@
106
111
  defaultDisplay="flex"
107
112
  @change="$emit('change')" />
108
113
 
109
- <ContextMenu ref="fieldOpt" position="bottom-right">
110
- <div class="flex flex-col gap-1 min-w-[120px]">
111
- <button class="p-3 bg-base-500 text-left pl-8 hover:bg-text-50"
112
- @click="item.props.fields.push({ type:'name', label:'Nama' });$emit('change')">
113
- Name
114
- </button>
115
- <button class="p-3 bg-base-500 text-left pl-8 hover:bg-text-50"
116
- @click="item.props.fields.push({ type:'mobileNumber', label:'Nomor HP' });$emit('change')">
117
- Mobile Number
118
- </button>
119
- <button class="p-3 bg-base-500 text-left pl-8 hover:bg-text-50"
120
- @click="item.props.fields.push({ type:'email', label:'Email' });$emit('change')">
121
- Email
122
- </button>
123
- <button class="p-3 bg-base-500 text-left pl-8 hover:bg-text-50"
124
- @click="item.props.fields.push({ type:'remark', label:'Pertanyaan' });$emit('change')">
125
- Pertanyaan
126
- </button>
127
- <button class="p-3 bg-base-500 text-left pl-8 hover:bg-text-50"
128
- @click="item.props.fields.push({ type:'referralCode', label:'Kode Referral (optional)' });$emit('change')">
129
- Referral Code
114
+ <ContextMenu ref="fieldOpt" position="bottom-right" class="border-text-200">
115
+ <div class="flex flex-col min-w-[120px] divide-y divide-text-100">
116
+ <button v-for="field in fields"
117
+ class="p-3 bg-base-500 text-left pl-8 hover:bg-text-50 disabled:bg-base-500 disabled:text-text-300"
118
+ @click="item.props.fields.push(field);$emit('change')"
119
+ :disabled="item.props.fields.findIndex((_) => _.type === field.type) >= 0">
120
+ {{ field.label }}
130
121
  </button>
131
122
  </div>
132
123
  </ContextMenu>
@@ -135,11 +126,62 @@
135
126
  <div class="flex flex-col gap-1 min-w-[120px]">
136
127
  <button class="p-3 bg-base-500 text-left pl-8 hover:bg-text-50"
137
128
  @click="item.props.onSubmit.push({ type:'send-whatsapp', _edit:true })">Send Whatsapp</button>
138
- <button class="p-3 bg-base-500 text-left pl-8 hover:bg-text-50"
129
+ <button class="p-3 bg-base-500 text-left pl-8 hover:bg-text-50" v-if="item.props.fields.includes('referralCode')"
139
130
  @click="item.props.onSubmit.push({ type:'send-whatsapp-referral' });$emit('change')">Send Whatsapp to Referral</button>
140
131
  </div>
141
132
  </ContextMenu>
142
133
 
134
+ <Modal ref="positionEdit" width="360" height="480">
135
+ <template v-slot:head>
136
+ <div class="relative p-5">
137
+ <h3>Daftar Posisi</h3>
138
+ <div class="absolute top-0 right-0 p-2">
139
+ <button type="button" class="p-2" @click="$refs.positionEdit.close()">
140
+ <svg width="24" height="24" viewBox="0 0 24 24" class="fill-text-300 hover:fill-red-500" xmlns="http://www.w3.org/2000/svg">
141
+ <path d="M6.53034 5.46965C6.23745 5.17676 5.76257 5.17676 5.46968 5.46965C5.17679 5.76255 5.17679 6.23742 5.46968 6.53031L10.9393 12L5.46967 17.4697C5.17678 17.7626 5.17678 18.2374 5.46967 18.5303C5.76256 18.8232 6.23744 18.8232 6.53033 18.5303L12 13.0606L17.4697 18.5303C17.7626 18.8232 18.2375 18.8232 18.5303 18.5303C18.8232 18.2374 18.8232 17.7626 18.5303 17.4697L13.0607 12L18.5303 6.53032C18.8232 6.23743 18.8232 5.76256 18.5303 5.46966C18.2374 5.17677 17.7626 5.17677 17.4697 5.46966L12 10.9393L6.53034 5.46965Z"/>
142
+ </svg>
143
+ </button>
144
+ </div>
145
+ </div>
146
+ </template>
147
+ <template v-slot:foot="{ context }">
148
+ <div class="p-5">
149
+ <Button type="button" class="w-[100px]" @click="$util.removeEmpty(context.items);$refs.positionEdit.close();$emit('change')">OK</Button>
150
+ </div>
151
+ </template>
152
+ <template #default="{ context }">
153
+ <div class="flex-1 p-5">
154
+ <ListItem :items="context.items"
155
+ body-class="flex flex-col gap-2"
156
+ @reorder="(from, to) => { context.items.splice(to, 0, context.items.splice(from, 1)[0]); }">
157
+ <template v-slot="{ item, index }">
158
+ <div class="flex flex-row items-center gap-2">
159
+ <div class="flex-1">
160
+ <Textbox v-model="item.text" @keyup.enter="context.items.push({})">
161
+ <template #start>
162
+ <div v-if="context.items.length > 1" data-reorder class="self-stretch flex items-center justify-center px-2 bg-text-50">
163
+ <svg width="14" height="14" class="fill-text-300" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 512 512"><path d="M496 288H16c-8.8 0-16 7.2-16 16v32c0 8.8 7.2 16 16 16h480c8.8 0 16-7.2 16-16v-32c0-8.8-7.2-16-16-16zm0-128H16c-8.8 0-16 7.2-16 16v32c0 8.8 7.2 16 16 16h480c8.8 0 16-7.2 16-16v-32c0-8.8-7.2-16-16-16z"/></svg>
164
+ </div>
165
+ </template>
166
+ </Textbox>
167
+ </div>
168
+ <div>
169
+ <button type="button" @click="context.items.splice(index, 1)">
170
+ <svg width="16" height="16" class="fill-text-300 hover:fill-red-500" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 320 512"><path d="M207.6 256l107.72-107.72c6.23-6.23 6.23-16.34 0-22.58l-25.03-25.03c-6.23-6.23-16.34-6.23-22.58 0L160 208.4 52.28 100.68c-6.23-6.23-16.34-6.23-22.58 0L4.68 125.7c-6.23 6.23-6.23 16.34 0 22.58L112.4 256 4.68 363.72c-6.23 6.23-6.23 16.34 0 22.58l25.03 25.03c6.23 6.23 16.34 6.23 22.58 0L160 303.6l107.72 107.72c6.23 6.23 16.34 6.23 22.58 0l25.03-25.03c6.23-6.23 6.23-16.34 0-22.58L207.6 256z"/></svg>
171
+ </button>
172
+ </div>
173
+ </div>
174
+ </template>
175
+ </ListItem>
176
+
177
+ <div class="text-center p-3">
178
+ <button type="button" class="text-primary" @click="context.items.push({})">Tambah Posisi</button>
179
+ </div>
180
+
181
+ </div>
182
+ </template>
183
+ </Modal>
184
+
143
185
  </div>
144
186
  </template>
145
187
 
@@ -164,6 +206,25 @@ export default{
164
206
 
165
207
  },
166
208
 
209
+ data(){
210
+ return {
211
+ fields: [
212
+ { type:'name', label:'Nama' },
213
+ { type:'mobileNumber', label:'Nomor HP' },
214
+ { type:'email', label:'Email' },
215
+ { type:'remark', label:'Pertanyaan' },
216
+ { type:'referralCode', label:'Kode Referral (optional)' },
217
+ { type:'ktpNumber', label:'Nomor KTP' },
218
+ { type:'birthDate', label:'Tempat/Tanggal Lahir' },
219
+ { type:'position', label:'Jabatan yang Dilamar', _edit:'positionEdit', items:[] },
220
+ { type:'provinceName', label:'Propinsi Domisili' },
221
+ { type:'education', label:'Pendidikan Terakhir', _edit:'positionEdit', items:[] },
222
+ { type:'major', label:'Jurusan', _edit:'positionEdit', items:[] },
223
+ { type:'university', label:'Nama Sekolah/Institusi/Universitas', _edit:'positionEdit', items:[] },
224
+ ]
225
+ }
226
+ },
227
+
167
228
  methods: {
168
229
 
169
230
  addField(type){
@@ -564,6 +564,7 @@ export default{
564
564
  },
565
565
 
566
566
  generateExtClass(props){
567
+ if(!props || typeof props !== 'object') return ''
567
568
 
568
569
  return [
569
570
  props.padding ? props.padding.join(' ') : '',
@@ -604,7 +605,6 @@ export default{
604
605
  ]
605
606
  .filter(_ => _)
606
607
  .join(' ')
607
-
608
608
  },
609
609
 
610
610
  setExtClass(components){
@@ -618,6 +618,14 @@ export default{
618
618
  comp.items = this.setExtClass(comp.items)
619
619
  }
620
620
 
621
+ if(comp.props && Array.isArray(comp.props.items)){
622
+ comp.props.items = this.setExtClass(comp.props.items)
623
+ }
624
+
625
+ if(comp.type === 'Carousel'){
626
+ console.log('Carousel', comp)
627
+ }
628
+
621
629
  return comp
622
630
  })
623
631
  },
@@ -815,16 +823,21 @@ export default{
815
823
 
816
824
  components: [
817
825
 
818
- { type:'Carousel', name:'Carousel', group:'Widgets', props:{
826
+ { type:'Carousel', name:'Carousel', group:'Widgets', items:[], props:{
819
827
  name: 'Untitled Carousel',
820
- ratio:[ '4/3', '8/3' ], items:[]
828
+ ratio:[ '4/3', '8/3' ]
821
829
  }},
822
830
 
823
831
  { type:'ContactForm', name:'Contact Form', group:'Widgets', props:{
824
832
  fields:[
825
- { type:'name', label:'Nama' },
826
- { type:'mobileNumber', label:'Nomor HP' }
833
+ { type:'name', label:'Nama', required:true },
834
+ { type:'mobileNumber', label:'Nomor HP', required:true },
835
+ { type:'remark', label:'Pertanyaan' },
827
836
  ],
837
+ title: 'Contact Us',
838
+ description: 'Lorem ipsum dolor sit amet, consectetur adipiscing elit. Praesent gravida erat eget nisi',
839
+ submitMethod: 'post',
840
+ submitUrl: '/inquiry',
828
841
  onSubmit:[],
829
842
  }},
830
843