forked from geolba/tethys.backend
- second commit
This commit is contained in:
parent
4fc3bb0a01
commit
59a99ff3c8
61 changed files with 2625 additions and 1182 deletions
|
@ -2,8 +2,8 @@
|
|||
// import { reactive, computed } from 'vue';
|
||||
// import { usePage } from '@inertiajs/vue3'
|
||||
// import { usePage } from '@inertiajs/inertia-vue3';
|
||||
import { LayoutService } from '@/Stores/layout.js';
|
||||
import menu from '@/menu.js'
|
||||
import { LayoutService } from '@/Stores/layout';
|
||||
import menu from '@/menu'
|
||||
import AsideMenuLayer from '@/Components/AsideMenuLayer.vue';
|
||||
import OverlayLayer from '@/Components/OverlayLayer.vue';
|
||||
|
||||
|
|
|
@ -3,7 +3,7 @@ import { ref, computed } from 'vue';
|
|||
import { Link } from '@inertiajs/vue3';
|
||||
// import { Link } from '@inertiajs/inertia-vue3';
|
||||
|
||||
import { StyleService } from '@/Stores/style.js';
|
||||
import { StyleService } from '@/Stores/style';
|
||||
import { mdiMinus, mdiPlus } from '@mdi/js';
|
||||
import { getButtonColor } from '@/colors.js';
|
||||
import BaseIcon from '@/Components/BaseIcon.vue';
|
||||
|
|
|
@ -4,8 +4,8 @@ import { router } from '@inertiajs/vue3'
|
|||
import { stardust } from '@eidellev/adonis-stardust/client';
|
||||
import { mdiLogout, mdiClose } from '@mdi/js';
|
||||
import { computed } from 'vue';
|
||||
import { LayoutService } from '@/Stores/layout.js';
|
||||
import { StyleService } from '@/Stores/style.js';
|
||||
import { LayoutService } from '@/Stores/layout';
|
||||
import { StyleService } from '@/Stores/style';
|
||||
import AsideMenuList from '@/Components/AsideMenuList.vue';
|
||||
import AsideMenuItem from '@/Components/AsideMenuItem.vue';
|
||||
import BaseIcon from '@/Components/BaseIcon.vue';
|
||||
|
|
|
@ -2,28 +2,28 @@
|
|||
import AsideMenuItem from '@/Components/AsideMenuItem.vue';
|
||||
|
||||
defineProps({
|
||||
isDropdownList: Boolean,
|
||||
menu: {
|
||||
type: Object,
|
||||
default: () => {}
|
||||
}
|
||||
})
|
||||
isDropdownList: Boolean,
|
||||
menu: {
|
||||
type: Object,
|
||||
default: () => {},
|
||||
},
|
||||
});
|
||||
|
||||
const emit = defineEmits(['menu-click'])
|
||||
const emit = defineEmits(['menu-click']);
|
||||
|
||||
const menuClick = (event, item) => {
|
||||
emit('menu-click', event, item)
|
||||
}
|
||||
emit('menu-click', event, item);
|
||||
};
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<ul>
|
||||
<AsideMenuItem
|
||||
v-for="(item, index) in menu"
|
||||
:key="index"
|
||||
v-bind:item="item"
|
||||
:is-dropdown-list="isDropdownList"
|
||||
@menu-click="menuClick"
|
||||
/>
|
||||
</ul>
|
||||
<ul>
|
||||
<AsideMenuItem
|
||||
v-for="(item, index) in menu"
|
||||
:key="index"
|
||||
v-bind:item="item"
|
||||
:is-dropdown-list="isDropdownList"
|
||||
@menu-click="menuClick"
|
||||
/>
|
||||
</ul>
|
||||
</template>
|
||||
|
|
|
@ -1,111 +1,81 @@
|
|||
<script setup>
|
||||
import { mdiCog } from '@mdi/js'
|
||||
import { computed, useSlots } from 'vue'
|
||||
import BaseIcon from '@/Components/BaseIcon.vue'
|
||||
import { mdiCog } from '@mdi/js';
|
||||
import { computed, useSlots } from 'vue';
|
||||
import BaseIcon from '@/Components/BaseIcon.vue';
|
||||
|
||||
const props = defineProps({
|
||||
title: {
|
||||
type: String,
|
||||
default: null
|
||||
},
|
||||
icon: {
|
||||
type: String,
|
||||
default: null
|
||||
},
|
||||
headerIcon: {
|
||||
type: String,
|
||||
default: null
|
||||
},
|
||||
rounded: {
|
||||
type: String,
|
||||
default: 'rounded-xl'
|
||||
},
|
||||
hasTable: Boolean,
|
||||
empty: Boolean,
|
||||
form: Boolean,
|
||||
hoverable: Boolean,
|
||||
modal: Boolean
|
||||
})
|
||||
title: {
|
||||
type: String,
|
||||
default: null,
|
||||
},
|
||||
icon: {
|
||||
type: String,
|
||||
default: null,
|
||||
},
|
||||
headerIcon: {
|
||||
type: String,
|
||||
default: null,
|
||||
},
|
||||
rounded: {
|
||||
type: String,
|
||||
default: 'rounded-xl',
|
||||
},
|
||||
hasFormData: Boolean,
|
||||
empty: Boolean,
|
||||
form: Boolean,
|
||||
hoverable: Boolean,
|
||||
modal: Boolean,
|
||||
});
|
||||
|
||||
const emit = defineEmits(['header-icon-click', 'submit'])
|
||||
const emit = defineEmits(['header-icon-click', 'submit']);
|
||||
|
||||
const is = computed(() => props.form ? 'form' : 'div')
|
||||
const is = computed(() => (props.form ? 'form' : 'div'));
|
||||
|
||||
const slots = useSlots()
|
||||
const slots = useSlots();
|
||||
|
||||
const footer = computed(() => slots.footer && !!slots.footer())
|
||||
const footer = computed(() => slots.footer && !!slots.footer());
|
||||
|
||||
const componentClass = computed(() => {
|
||||
const base = [
|
||||
props.rounded,
|
||||
props.modal ? 'dark:bg-slate-900' : 'dark:bg-slate-900/70'
|
||||
]
|
||||
const base = [props.rounded, props.modal ? 'dark:bg-slate-900' : 'dark:bg-slate-900/70'];
|
||||
|
||||
if (props.hoverable) {
|
||||
base.push('hover:shadow-lg transition-shadow duration-500')
|
||||
}
|
||||
if (props.hoverable) {
|
||||
base.push('hover:shadow-lg transition-shadow duration-500');
|
||||
}
|
||||
|
||||
return base
|
||||
})
|
||||
return base;
|
||||
});
|
||||
|
||||
const computedHeaderIcon = computed(() => props.headerIcon ?? mdiCog)
|
||||
const computedHeaderIcon = computed(() => props.headerIcon ?? mdiCog);
|
||||
|
||||
const headerIconClick = () => {
|
||||
emit('header-icon-click')
|
||||
}
|
||||
emit('header-icon-click');
|
||||
};
|
||||
|
||||
const submit = e => {
|
||||
emit('submit', e)
|
||||
}
|
||||
const submit = (e) => {
|
||||
emit('submit', e);
|
||||
};
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<component
|
||||
:is="is"
|
||||
:class="componentClass"
|
||||
class="bg-white flex flex-col"
|
||||
@submit="submit"
|
||||
>
|
||||
<header
|
||||
v-if="title"
|
||||
class="flex items-stretch border-b border-gray-100 dark:border-slate-800"
|
||||
>
|
||||
<div
|
||||
class="flex items-center py-3 grow font-bold"
|
||||
:class="[ icon ? 'px-4' : 'px-6' ]"
|
||||
>
|
||||
<BaseIcon
|
||||
v-if="icon"
|
||||
:path="icon"
|
||||
class="mr-3"
|
||||
/>
|
||||
{{ title }}
|
||||
</div>
|
||||
<button
|
||||
class="flex items-center py-3 px-4 justify-center ring-blue-700 focus:ring"
|
||||
@click="headerIconClick"
|
||||
>
|
||||
<BaseIcon :path="computedHeaderIcon" />
|
||||
</button>
|
||||
</header>
|
||||
<div
|
||||
v-if="empty"
|
||||
class="text-center py-24 text-gray-500 dark:text-slate-400"
|
||||
>
|
||||
<p>Nothing's here…</p>
|
||||
</div>
|
||||
<div
|
||||
v-else
|
||||
class="flex-1"
|
||||
:class="{'p-6':!hasTable}"
|
||||
>
|
||||
<slot />
|
||||
</div>
|
||||
<div
|
||||
v-if="footer"
|
||||
class="p-6 border-t border-gray-100 dark:border-slate-800"
|
||||
>
|
||||
<slot name="footer" />
|
||||
</div>
|
||||
</component>
|
||||
<component :is="is" :class="componentClass" class="bg-white flex flex-col" @submit="submit">
|
||||
<header v-if="title" class="flex items-stretch border-b border-gray-100 dark:border-slate-800">
|
||||
<div class="flex items-center py-3 grow font-bold" :class="[icon ? 'px-4' : 'px-6']">
|
||||
<BaseIcon v-if="icon" :path="icon" class="mr-3" />
|
||||
{{ title }}
|
||||
</div>
|
||||
<button class="flex items-center py-3 px-4 justify-center ring-blue-700 focus:ring" @click="headerIconClick">
|
||||
<BaseIcon :path="computedHeaderIcon" />
|
||||
</button>
|
||||
</header>
|
||||
<div v-if="empty" class="text-center py-24 text-gray-500 dark:text-slate-400">
|
||||
<p>Nothing's here…</p>
|
||||
</div>
|
||||
<!-- <div v-else class="flex-1" :class="{'p-6':!hasTable}"> -->
|
||||
<div v-else class="flex-1" :class="[!hasFormData && 'p-6']">
|
||||
<slot />
|
||||
</div>
|
||||
<div v-if="footer" class="p-6 border-t border-gray-100 dark:border-slate-800">
|
||||
<slot name="footer" />
|
||||
</div>
|
||||
</component>
|
||||
</template>
|
||||
|
|
|
@ -1,56 +1,62 @@
|
|||
<script setup>
|
||||
import { computed } from 'vue'
|
||||
import { mdiClose } from '@mdi/js'
|
||||
import BaseButton from '@/Components/BaseButton.vue'
|
||||
import BaseButtons from '@/Components/BaseButtons.vue'
|
||||
import CardBox from '@/Components/CardBox.vue'
|
||||
import OverlayLayer from '@/Components/OverlayLayer.vue'
|
||||
<script setup lang="ts">
|
||||
import { computed } from 'vue';
|
||||
import { mdiClose } from '@mdi/js';
|
||||
import BaseButton from '@/Components/BaseButton.vue';
|
||||
import BaseButtons from '@/Components/BaseButtons.vue';
|
||||
import CardBox from '@/Components/CardBox.vue';
|
||||
import OverlayLayer from '@/Components/OverlayLayer.vue';
|
||||
|
||||
const props = defineProps({
|
||||
title: {
|
||||
type: String,
|
||||
default: null
|
||||
default: null,
|
||||
},
|
||||
largeTitle: {
|
||||
type: String,
|
||||
default: null
|
||||
default: null,
|
||||
},
|
||||
button: {
|
||||
type: String,
|
||||
default: 'info'
|
||||
default: 'info',
|
||||
},
|
||||
buttonLabel: {
|
||||
type: String,
|
||||
default: 'Done'
|
||||
default: 'Done',
|
||||
},
|
||||
hasCancel: Boolean,
|
||||
modelValue: {
|
||||
type: [String, Number, Boolean],
|
||||
default: null
|
||||
}
|
||||
})
|
||||
default: null,
|
||||
},
|
||||
});
|
||||
|
||||
const emit = defineEmits(['update:modelValue', 'cancel', 'confirm'])
|
||||
const emit = defineEmits(['update:modelValue', 'cancel', 'confirm']);
|
||||
|
||||
const value = computed({
|
||||
get: () => props.modelValue,
|
||||
set: value => emit('update:modelValue', value)
|
||||
})
|
||||
set: (value) => emit('update:modelValue', value),
|
||||
});
|
||||
|
||||
const confirmCancel = (mode) => {
|
||||
value.value = false;
|
||||
emit(mode);
|
||||
}
|
||||
};
|
||||
|
||||
const confirm = () => confirmCancel('confirm')
|
||||
const confirm = () => confirmCancel('confirm');
|
||||
|
||||
const cancel = () => confirmCancel('cancel')
|
||||
const cancel = () => confirmCancel('cancel');
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<OverlayLayer v-show="value" @overlay-click="cancel">
|
||||
<CardBox v-show="value" :title="title" class="shadow-lg max-h-modal w-11/12 md:w-3/5 lg:w-2/5 xl:w-4/12 z-50"
|
||||
:header-icon="mdiClose" modal @header-icon-click="cancel">
|
||||
<CardBox
|
||||
v-show="value"
|
||||
:title="title"
|
||||
class="shadow-lg max-h-modal w-11/12 md:w-3/5 lg:w-2/5 xl:w-4/12 z-50"
|
||||
:header-icon="mdiClose"
|
||||
modal
|
||||
@header-icon-click="cancel"
|
||||
>
|
||||
<div class="space-y-3">
|
||||
<h1 v-if="largeTitle" class="text-2xl">
|
||||
{{ largeTitle }}
|
||||
|
|
|
@ -3,66 +3,71 @@ import { computed, ref, onMounted, onBeforeUnmount } from 'vue';
|
|||
import { MainService } from '@/Stores/main';
|
||||
import FormControlIcon from '@/Components/FormControlIcon.vue';
|
||||
const props = defineProps({
|
||||
name: {
|
||||
type: String,
|
||||
default: null,
|
||||
},
|
||||
id: {
|
||||
type: String,
|
||||
default: null,
|
||||
},
|
||||
autocomplete: {
|
||||
type: String,
|
||||
default: null,
|
||||
},
|
||||
placeholder: {
|
||||
type: String,
|
||||
default: null,
|
||||
},
|
||||
inputmode: {
|
||||
type: String,
|
||||
default: null,
|
||||
},
|
||||
icon: {
|
||||
type: String,
|
||||
default: null,
|
||||
},
|
||||
options: {
|
||||
type: Array,
|
||||
default: null,
|
||||
},
|
||||
type: {
|
||||
type: String,
|
||||
default: 'text',
|
||||
},
|
||||
modelValue: {
|
||||
type: [String, Number, Boolean, Array, Object],
|
||||
default: '',
|
||||
},
|
||||
required: Boolean,
|
||||
borderless: Boolean,
|
||||
transparent: Boolean,
|
||||
ctrlKFocus: Boolean,
|
||||
name: {
|
||||
type: String,
|
||||
default: null,
|
||||
},
|
||||
id: {
|
||||
type: String,
|
||||
default: null,
|
||||
},
|
||||
autocomplete: {
|
||||
type: String,
|
||||
default: null,
|
||||
},
|
||||
placeholder: {
|
||||
type: String,
|
||||
default: null,
|
||||
},
|
||||
inputmode: {
|
||||
type: String,
|
||||
default: null,
|
||||
},
|
||||
icon: {
|
||||
type: String,
|
||||
default: null,
|
||||
},
|
||||
options: {
|
||||
type: [Array, Object],
|
||||
default: null,
|
||||
},
|
||||
type: {
|
||||
type: String,
|
||||
default: 'text',
|
||||
},
|
||||
isReadOnly: {
|
||||
type: Boolean,
|
||||
default: false,
|
||||
},
|
||||
modelValue: {
|
||||
type: [String, Number, Boolean, Array, Object],
|
||||
default: '',
|
||||
},
|
||||
required: Boolean,
|
||||
borderless: Boolean,
|
||||
transparent: Boolean,
|
||||
ctrlKFocus: Boolean,
|
||||
});
|
||||
const emit = defineEmits(['update:modelValue', 'setRef']);
|
||||
const computedValue = computed({
|
||||
get: () => props.modelValue,
|
||||
set: (value) => {
|
||||
emit('update:modelValue', value);
|
||||
},
|
||||
get: () => props.modelValue,
|
||||
set: (value) => {
|
||||
emit('update:modelValue', value);
|
||||
},
|
||||
});
|
||||
const inputElClass = computed(() => {
|
||||
const base = [
|
||||
'px-3 py-2 max-w-full focus:ring focus:outline-none border-gray-700 rounded w-full',
|
||||
'dark:placeholder-gray-400',
|
||||
computedType.value === 'textarea' ? 'h-24' : 'h-12',
|
||||
props.borderless ? 'border-0' : 'border',
|
||||
props.transparent ? 'bg-transparent' : 'bg-white dark:bg-slate-800',
|
||||
];
|
||||
if (props.icon) {
|
||||
base.push('pl-10');
|
||||
}
|
||||
return base;
|
||||
const base = [
|
||||
'px-3 py-2 max-w-full focus:ring focus:outline-none border-gray-700 rounded w-full',
|
||||
'dark:placeholder-gray-400',
|
||||
computedType.value === 'textarea' ? 'h-24' : 'h-12',
|
||||
props.borderless ? 'border-0' : 'border',
|
||||
// props.transparent && !props.isReadOnly ? 'bg-transparent' : 'bg-white dark:bg-slate-800',
|
||||
props.isReadOnly ? 'bg-gray-50 dark:bg-slate-600' : 'bg-white dark:bg-slate-800',
|
||||
];
|
||||
if (props.icon) {
|
||||
base.push('pl-10');
|
||||
}
|
||||
return base;
|
||||
});
|
||||
const computedType = computed(() => (props.options ? 'select' : props.type));
|
||||
const controlIconH = computed(() => (props.type === 'textarea' ? 'h-full' : 'h-12'));
|
||||
|
@ -71,74 +76,70 @@ const selectEl = ref(null);
|
|||
const textareaEl = ref(null);
|
||||
const inputEl = ref(null);
|
||||
onMounted(() => {
|
||||
if (selectEl.value) {
|
||||
emit('setRef', selectEl.value);
|
||||
} else if (textareaEl.value) {
|
||||
emit('setRef', textareaEl.value);
|
||||
} else {
|
||||
emit('setRef', inputEl.value);
|
||||
}
|
||||
if (selectEl.value) {
|
||||
emit('setRef', selectEl.value);
|
||||
} else if (textareaEl.value) {
|
||||
emit('setRef', textareaEl.value);
|
||||
} else {
|
||||
emit('setRef', inputEl.value);
|
||||
}
|
||||
});
|
||||
if (props.ctrlKFocus) {
|
||||
const fieldFocusHook = (e) => {
|
||||
if (e.ctrlKey && e.key === 'k') {
|
||||
e.preventDefault();
|
||||
inputEl.value.focus();
|
||||
} else if (e.key === 'Escape') {
|
||||
inputEl.value.blur();
|
||||
}
|
||||
};
|
||||
onMounted(() => {
|
||||
if (!mainService.isFieldFocusRegistered) {
|
||||
window.addEventListener('keydown', fieldFocusHook);
|
||||
mainService.isFieldFocusRegistered = true;
|
||||
} else {
|
||||
// console.error('Duplicate field focus event')
|
||||
}
|
||||
});
|
||||
onBeforeUnmount(() => {
|
||||
window.removeEventListener('keydown', fieldFocusHook);
|
||||
mainService.isFieldFocusRegistered = false;
|
||||
});
|
||||
const fieldFocusHook = (e) => {
|
||||
if (e.ctrlKey && e.key === 'k') {
|
||||
e.preventDefault();
|
||||
inputEl.value.focus();
|
||||
} else if (e.key === 'Escape') {
|
||||
inputEl.value.blur();
|
||||
}
|
||||
};
|
||||
onMounted(() => {
|
||||
if (!mainService.isFieldFocusRegistered) {
|
||||
window.addEventListener('keydown', fieldFocusHook);
|
||||
mainService.isFieldFocusRegistered = true;
|
||||
} else {
|
||||
// console.error('Duplicate field focus event')
|
||||
}
|
||||
});
|
||||
onBeforeUnmount(() => {
|
||||
window.removeEventListener('keydown', fieldFocusHook);
|
||||
mainService.isFieldFocusRegistered = false;
|
||||
});
|
||||
}
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div class="relative">
|
||||
<select
|
||||
v-if="computedType === 'select'"
|
||||
:id="id"
|
||||
v-model="computedValue"
|
||||
:name="name"
|
||||
:class="inputElClass"
|
||||
>
|
||||
<option v-for="option in options" :key="option.id ?? option" :value="option">
|
||||
{{ option.label ?? option }}
|
||||
</option>
|
||||
</select>
|
||||
<textarea
|
||||
v-else-if="computedType === 'textarea'"
|
||||
:id="id"
|
||||
v-model="computedValue"
|
||||
:class="inputElClass"
|
||||
:name="name"
|
||||
:placeholder="placeholder"
|
||||
:required="required"
|
||||
/>
|
||||
<input
|
||||
v-else
|
||||
:id="id"
|
||||
ref="inputEl"
|
||||
v-model="computedValue"
|
||||
:name="name"
|
||||
:inputmode="inputmode"
|
||||
:autocomplete="autocomplete"
|
||||
:required="required"
|
||||
:placeholder="placeholder"
|
||||
:type="computedType"
|
||||
:class="inputElClass"
|
||||
/>
|
||||
<FormControlIcon v-if="icon" :icon="icon" :h="controlIconH" />
|
||||
<slot />
|
||||
</div>
|
||||
<div class="relative">
|
||||
<select v-if="computedType === 'select'" :id="id" v-model="computedValue" :name="name" :class="inputElClass">
|
||||
<option v-if="placeholder" class="text-opacity-25" value="" disabled selected>{{ placeholder }}</option>
|
||||
<option v-for="option in options" :key="option.id ?? option" :value="option.value ?? option">
|
||||
{{ option.label ?? option }}
|
||||
</option>
|
||||
</select>
|
||||
<textarea
|
||||
v-else-if="computedType === 'textarea'"
|
||||
:id="id"
|
||||
v-model="computedValue"
|
||||
:class="inputElClass"
|
||||
:name="name"
|
||||
:placeholder="placeholder"
|
||||
:required="required"
|
||||
/>
|
||||
<input
|
||||
v-else
|
||||
:id="id"
|
||||
ref="inputEl"
|
||||
v-model="computedValue"
|
||||
:name="name"
|
||||
:inputmode="inputmode"
|
||||
:autocomplete="autocomplete"
|
||||
:required="required"
|
||||
:placeholder="placeholder"
|
||||
:type="computedType"
|
||||
:class="inputElClass"
|
||||
:readonly="isReadOnly"
|
||||
/>
|
||||
<FormControlIcon v-if="icon" :icon="icon" :h="controlIconH" />
|
||||
<slot />
|
||||
</div>
|
||||
</template>
|
||||
|
|
|
@ -2,46 +2,47 @@
|
|||
import { computed, useSlots } from 'vue';
|
||||
|
||||
defineProps({
|
||||
label: {
|
||||
type: String,
|
||||
default: null,
|
||||
},
|
||||
labelFor: {
|
||||
type: String,
|
||||
default: null,
|
||||
},
|
||||
help: {
|
||||
type: String,
|
||||
default: null,
|
||||
},
|
||||
label: {
|
||||
type: String,
|
||||
default: null,
|
||||
},
|
||||
labelFor: {
|
||||
type: String,
|
||||
default: null,
|
||||
},
|
||||
help: {
|
||||
type: String,
|
||||
default: null,
|
||||
},
|
||||
});
|
||||
|
||||
const slots = useSlots();
|
||||
|
||||
const wrapperClass = computed(() => {
|
||||
const base = [];
|
||||
const slotsLength = slots.default().length;
|
||||
const base = [];
|
||||
const slotsLength = slots.default().length;
|
||||
|
||||
if (slotsLength > 1) {
|
||||
base.push('grid grid-cols-1 gap-3');
|
||||
}
|
||||
if (slotsLength > 1) {
|
||||
base.push('grid grid-cols-1 gap-3');
|
||||
}
|
||||
|
||||
if (slotsLength === 2) {
|
||||
base.push('md:grid-cols-2');
|
||||
}
|
||||
if (slotsLength === 2) {
|
||||
base.push('md:grid-cols-2');
|
||||
}
|
||||
|
||||
return base;
|
||||
return base;
|
||||
});
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div class="mb-6 last:mb-0">
|
||||
<label v-if="label" :for="labelFor" class="block font-bold mb-2">{{ label }}</label>
|
||||
<div v-bind:class="wrapperClass">
|
||||
<slot />
|
||||
</div>
|
||||
<div v-if="help" class="text-xs text-gray-500 dark:text-slate-400 mt-1">
|
||||
{{ help }}
|
||||
</div>
|
||||
</div>
|
||||
<div class="mb-6 last:mb-0">
|
||||
<!-- <label v-if="label" :for="labelFor" class="block font-bold mb-2">{{ label }}</label> -->
|
||||
<label v-if="label" :for="labelFor" class="font-bold h-6 mt-3 text-xs leading-8 uppercase">{{ label }}</label>
|
||||
<div v-bind:class="wrapperClass">
|
||||
<slot />
|
||||
</div>
|
||||
<div v-if="help" class="text-xs text-gray-500 dark:text-slate-400 mt-1">
|
||||
{{ help }}
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
|
44
resources/js/Components/Icons/Confirm.vue
Normal file
44
resources/js/Components/Icons/Confirm.vue
Normal file
|
@ -0,0 +1,44 @@
|
|||
<template>
|
||||
<svg
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
width="100%"
|
||||
height="100%"
|
||||
fill="none"
|
||||
viewBox="0 0 24 24"
|
||||
stroke="currentColor"
|
||||
stroke-width="2"
|
||||
stroke-linecap="round"
|
||||
stroke-linejoin="round"
|
||||
class="my-svg-component"
|
||||
>
|
||||
<ellipse cx="12" cy="5" rx="9" ry="3"></ellipse>
|
||||
<path d="M21 12c0 1.66-4 3-9 3s-9-1.34-9-3"></path>
|
||||
<path d="M3 5v14c0 1.66 4 3 9 3s9-1.34 9-3V5"></path>
|
||||
</svg>
|
||||
</template>
|
||||
|
||||
<script lang="ts">
|
||||
export default {
|
||||
name: 'Icon_Mandatory',
|
||||
};
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.my-svg-component {
|
||||
/* Scoped CSS here */
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
fill: none;
|
||||
stroke: currentColor;
|
||||
stroke-width: 1;
|
||||
stroke-linecap: round;
|
||||
stroke-linejoin: round;
|
||||
}
|
||||
|
||||
/* path,
|
||||
circle {
|
||||
stroke: #ffffff;
|
||||
stroke-width: 6px;
|
||||
fill: none;
|
||||
} */
|
||||
</style>
|
35
resources/js/Components/Icons/Language.vue
Normal file
35
resources/js/Components/Icons/Language.vue
Normal file
|
@ -0,0 +1,35 @@
|
|||
<template>
|
||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" class="my-svg-component">
|
||||
<path
|
||||
fill-rule="evenodd"
|
||||
d="M9 2.25a.75.75 0 01.75.75v1.506a49.38 49.38 0 015.343.371.75.75 0 11-.186 1.489c-.66-.083-1.323-.151-1.99-.206a18.67 18.67 0 01-2.969 6.323c.317.384.65.753.998 1.107a.75.75 0 11-1.07 1.052A18.902 18.902 0 019 13.687a18.823 18.823 0 01-5.656 4.482.75.75 0 11-.688-1.333 17.323 17.323 0 005.396-4.353A18.72 18.72 0 015.89 8.598a.75.75 0 011.388-.568A17.21 17.21 0 009 11.224a17.17 17.17 0 002.391-5.165 48.038 48.038 0 00-8.298.307.75.75 0 01-.186-1.489 49.159 49.159 0 015.343-.371V3A.75.75 0 019 2.25zM15.75 9a.75.75 0 01.68.433l5.25 11.25a.75.75 0 01-1.36.634l-1.198-2.567h-6.744l-1.198 2.567a.75.75 0 01-1.36-.634l5.25-11.25A.75.75 0 0115.75 9zm-2.672 8.25h5.344l-2.672-5.726-2.672 5.726z"
|
||||
clip-rule="evenodd"
|
||||
/>
|
||||
</svg>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
export default {
|
||||
name: 'Icon_Mandatory',
|
||||
};
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.my-svg-component {
|
||||
/* Scoped CSS here */
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
fill: none;
|
||||
stroke: currentColor;
|
||||
stroke-width: 1;
|
||||
stroke-linecap: round;
|
||||
stroke-linejoin: round;
|
||||
}
|
||||
|
||||
/* path,
|
||||
circle {
|
||||
stroke: #ffffff;
|
||||
stroke-width: 6px;
|
||||
fill: none;
|
||||
} */
|
||||
</style>
|
35
resources/js/Components/Icons/Mandatory.vue
Normal file
35
resources/js/Components/Icons/Mandatory.vue
Normal file
|
@ -0,0 +1,35 @@
|
|||
<template>
|
||||
<svg class="my-svg-component" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24">
|
||||
<path
|
||||
stroke-linecap="round"
|
||||
stroke-linejoin="round"
|
||||
d="M11.42 15.17L17.25 21A2.652 2.652 0 0021 17.25l-5.877-5.877M11.42 15.17l2.496-3.03c.317-.384.74-.626 1.208-.766M11.42 15.17l-4.655 5.653a2.548 2.548 0 11-3.586-3.586l6.837-5.63m5.108-.233c.55-.164 1.163-.188 1.743-.14a4.5 4.5 0 004.486-6.336l-3.276 3.277a3.004 3.004 0 01-2.25-2.25l3.276-3.276a4.5 4.5 0 00-6.336 4.486c.091 1.076-.071 2.264-.904 2.95l-.102.085m-1.745 1.437L5.909 7.5H4.5L2.25 3.75l1.5-1.5L7.5 4.5v1.409l4.26 4.26m-1.745 1.437l1.745-1.437m6.615 8.206L15.75 15.75M4.867 19.125h.008v.008h-.008v-.008z"
|
||||
/>
|
||||
</svg>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
export default {
|
||||
name: 'Icon_Mandatory',
|
||||
};
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.my-svg-component {
|
||||
/* Scoped CSS here */
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
fill: none;
|
||||
stroke: currentColor;
|
||||
stroke-width: 2;
|
||||
stroke-linecap: round;
|
||||
stroke-linejoin: round;
|
||||
}
|
||||
|
||||
/* path,
|
||||
circle {
|
||||
stroke: #ffffff;
|
||||
stroke-width: 6px;
|
||||
fill: none;
|
||||
} */
|
||||
</style>
|
33
resources/js/Components/Icons/Recommendet.vue
Normal file
33
resources/js/Components/Icons/Recommendet.vue
Normal file
|
@ -0,0 +1,33 @@
|
|||
<template>
|
||||
<svg class="my-svg-component" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24">
|
||||
<path
|
||||
d="M6 3a3 3 0 00-3 3v2.25a3 3 0 003 3h2.25a3 3 0 003-3V6a3 3 0 00-3-3H6zM15.75 3a3 3 0 00-3 3v2.25a3 3 0 003 3H18a3 3 0 003-3V6a3 3 0 00-3-3h-2.25zM6 12.75a3 3 0 00-3 3V18a3 3 0 003 3h2.25a3 3 0 003-3v-2.25a3 3 0 00-3-3H6zM17.625 13.5a.75.75 0 00-1.5 0v2.625H13.5a.75.75 0 000 1.5h2.625v2.625a.75.75 0 001.5 0v-2.625h2.625a.75.75 0 000-1.5h-2.625V13.5z"
|
||||
/>
|
||||
</svg>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
export default {
|
||||
name: 'Icon_Mandatory',
|
||||
};
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.my-svg-component {
|
||||
/* Scoped CSS here */
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
fill: none;
|
||||
stroke: currentColor;
|
||||
stroke-width: 2;
|
||||
stroke-linecap: round;
|
||||
stroke-linejoin: round;
|
||||
}
|
||||
|
||||
/* path,
|
||||
circle {
|
||||
stroke: #ffffff;
|
||||
stroke-width: 6px;
|
||||
fill: none;
|
||||
} */
|
||||
</style>
|
49
resources/js/Components/Icons/Wizard.vue
Normal file
49
resources/js/Components/Icons/Wizard.vue
Normal file
|
@ -0,0 +1,49 @@
|
|||
<template>
|
||||
<div class="flex items-center relative">
|
||||
<!-- v-bind:class="{ 'text-white bg-teal-600 border-teal-600': isCurrent, 'border-teal-600': isChecked }" -->
|
||||
<div
|
||||
class="text-gray-500 rounded-full transition duration-500 ease-in-out h-12 w-12 py-3 border-2"
|
||||
:class="[
|
||||
isCurrent ? 'text-white bg-teal-600 border-teal-600' : 'border-gray-300',
|
||||
isChecked && 'text-teal-600 border-teal-600',
|
||||
]"
|
||||
>
|
||||
<!-- <svg class="my-svg-component" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24">
|
||||
<path
|
||||
stroke-linecap="round"
|
||||
stroke-linejoin="round"
|
||||
d="M11.42 15.17L17.25 21A2.652 2.652 0 0021 17.25l-5.877-5.877M11.42 15.17l2.496-3.03c.317-.384.74-.626 1.208-.766M11.42 15.17l-4.655 5.653a2.548 2.548 0 11-3.586-3.586l6.837-5.63m5.108-.233c.55-.164 1.163-.188 1.743-.14a4.5 4.5 0 004.486-6.336l-3.276 3.277a3.004 3.004 0 01-2.25-2.25l3.276-3.276a4.5 4.5 0 00-6.336 4.486c.091 1.076-.071 2.264-.904 2.95l-.102.085m-1.745 1.437L5.909 7.5H4.5L2.25 3.75l1.5-1.5L7.5 4.5v1.409l4.26 4.26m-1.745 1.437l1.745-1.437m6.615 8.206L15.75 15.75M4.867 19.125h.008v.008h-.008v-.008z"
|
||||
/>
|
||||
</svg> -->
|
||||
<slot></slot>
|
||||
<div class="absolute top-0 -ml-10 text-center mt-16 w-32 text-xs font-medium uppercase invisible sm:visible">
|
||||
{{ label }}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div v-if="!isLastStep"
|
||||
class="flex-auto border-t-2 transition duration-500 ease-in-out invisible sm:visible"
|
||||
:class="[isChecked ? 'border-teal-600' : 'border-gray-300']"
|
||||
></div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
export default {
|
||||
name: 'Icon_Multistep',
|
||||
|
||||
props: {
|
||||
isCurrent: Boolean,
|
||||
isChecked: Boolean,
|
||||
isLastStep: Boolean,
|
||||
label: String,
|
||||
},
|
||||
|
||||
data() {
|
||||
return {
|
||||
mode: 'light',
|
||||
checkedClass: 'border-teal-600',
|
||||
uncheckedClass: 'border-gray-300',
|
||||
};
|
||||
},
|
||||
};
|
||||
</script>
|
|
@ -7,8 +7,8 @@ import {ComputedRef} from "vue";
|
|||
import { computed, ref } from 'vue';
|
||||
import { containerMaxW } from '@/config.js';
|
||||
// import { MainService } from '@/Stores/main.js';
|
||||
import { StyleService } from '@/Stores/style.js';
|
||||
import { LayoutService } from '@/Stores/layout.js';
|
||||
import { StyleService } from '@/Stores/style';
|
||||
import { LayoutService } from '@/Stores/layout';
|
||||
import {
|
||||
mdiForwardburger,
|
||||
mdiBackburger,
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
<script setup>
|
||||
import { StyleService } from '@/Stores/style.js'
|
||||
import { StyleService } from '@/Stores/style'
|
||||
// import { Link } from '@inertiajs/vue3'
|
||||
import { Link } from '@inertiajs/vue3'
|
||||
import { computed } from 'vue'
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
<script setup>
|
||||
import { StyleService } from '@/Stores/style.js'
|
||||
import { StyleService } from '@/Stores/style'
|
||||
import { computed, ref, onMounted, onBeforeUnmount } from 'vue'
|
||||
import { mdiChevronUp, mdiChevronDown } from '@mdi/js'
|
||||
import NavBarItem from '@/Components/NavBarItem.vue'
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
<script setup>
|
||||
import { StyleService } from '@/Stores/style.js';
|
||||
import { StyleService } from '@/Stores/style';
|
||||
|
||||
defineProps({
|
||||
zIndex: {
|
||||
|
|
281
resources/js/Components/SearchAutocomplete.vue
Normal file
281
resources/js/Components/SearchAutocomplete.vue
Normal file
|
@ -0,0 +1,281 @@
|
|||
<template>
|
||||
<!-- <input
|
||||
v-model="data.search"
|
||||
@change="onChange"
|
||||
type="text"
|
||||
class="text-base font-medium block w-full rounded-md border transition ease-in-out focus:ring-1 border-gray-300 border-solid py-2 px-3 text-gray-700 placeholder-gray-400 focus:border-blue-200 focus:ring-blue-500 focus:outline-none"
|
||||
v-bind:name="props.name"
|
||||
/>
|
||||
<ul v-if="data.isOpen" class="mt-1 border-2 border-slate-50 overflow-auto shadow-lg rounded list-none">
|
||||
<li
|
||||
:class="['hover:bg-blue-100 hover:text-blue-800', 'w-full list-none text-left py-2 px-3 cursor-pointer']"
|
||||
v-for="(result, i) in data.results"
|
||||
:key="i"
|
||||
>
|
||||
{{ result.name }}
|
||||
</li>
|
||||
</ul> -->
|
||||
|
||||
<!-- <div class="flex-col justify-center relative"> -->
|
||||
<div class="relative">
|
||||
<input
|
||||
v-model="data.search"
|
||||
type="text"
|
||||
:class="inputElClass"
|
||||
:name="props.name"
|
||||
:placeholder="placeholder"
|
||||
autocomplete="off"
|
||||
@keydown.down="onArrowDown"
|
||||
@keydown.up="onArrowUp"
|
||||
/>
|
||||
<svg
|
||||
class="w-4 h-4 absolute left-2.5 top-3.5"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
fill="none"
|
||||
viewBox="0 0 24 24"
|
||||
stroke="currentColor"
|
||||
>
|
||||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M21 21l-6-6m2-5a7 7 0 11-14 0 7 7 0 0114 0z" />
|
||||
</svg>
|
||||
</div>
|
||||
<!-- <ul v-if="data.isOpen" class="bg-white border border-gray-100 w-full mt-2 max-h-28 overflow-y-auto"> -->
|
||||
<!-- :ref="(el) => { ul[i] = el }" -->
|
||||
<ul v-if="data.isOpen" class="bg-white dark:bg-slate-800 w-full mt-2 max-h-28 overflow-y-auto scroll-smooth">
|
||||
<li
|
||||
class="pl-8 pr-2 py-1 border-b-2 border-gray-100 relative cursor-pointer hover:bg-yellow-50 hover:text-gray-900"
|
||||
:class="{
|
||||
'bg-yellow-50 text-gray-900': i == selectedIndex,
|
||||
}"
|
||||
v-for="(result, i) in data.results"
|
||||
:key="i"
|
||||
:ref="
|
||||
(el: HTMLLIElement | null) => {
|
||||
ul[i] = el;
|
||||
}
|
||||
"
|
||||
>
|
||||
<svg class="absolute w-4 h-4 left-2 top-2" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 20 20" fill="currentColor">
|
||||
<path
|
||||
fill-rule="evenodd"
|
||||
d="M12.293 5.293a1 1 0 011.414 0l4 4a1 1 0 010 1.414l-4 4a1 1 0 01-1.414-1.414L14.586 11H3a1 1 0 110-2h11.586l-2.293-2.293a1 1 0 010-1.414z"
|
||||
clip-rule="evenodd"
|
||||
/>
|
||||
</svg>
|
||||
<!-- <b>Gar</b>{{ result.name }} -->
|
||||
|
||||
<!-- <span>
|
||||
{{ makeBold(result.name) }}
|
||||
</span> -->
|
||||
<span
|
||||
v-for="(item, index) in makeBold(result.name)"
|
||||
:key="index"
|
||||
:class="{
|
||||
'font-bold': data.search.toLowerCase().includes(item.toLowerCase()),
|
||||
'is-active': index == selectedIndex,
|
||||
}"
|
||||
>
|
||||
{{ item }}
|
||||
</span>
|
||||
</li>
|
||||
</ul>
|
||||
<!-- </div> -->
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { reactive, ref, computed, Ref, watch } from 'vue';
|
||||
import axios from 'axios';
|
||||
let props = defineProps({
|
||||
name: {
|
||||
type: String,
|
||||
required: false,
|
||||
default: 'autocomplete',
|
||||
},
|
||||
source: {
|
||||
type: [String, Array, Function],
|
||||
required: true,
|
||||
default: '',
|
||||
},
|
||||
label: {
|
||||
type: String,
|
||||
required: false,
|
||||
default: 'name',
|
||||
},
|
||||
responseProperty: {
|
||||
type: String,
|
||||
required: false,
|
||||
default: 'name',
|
||||
},
|
||||
placeholder: {
|
||||
type: String,
|
||||
default: null,
|
||||
},
|
||||
icon: {
|
||||
type: String,
|
||||
default: null,
|
||||
},
|
||||
required: Boolean,
|
||||
borderless: Boolean,
|
||||
transparent: Boolean,
|
||||
ctrlKFocus: Boolean,
|
||||
});
|
||||
|
||||
const inputElClass = computed(() => {
|
||||
const base = [
|
||||
'px-3 py-2 max-w-full focus:ring focus:outline-none border-gray-700 rounded w-full',
|
||||
'dark:placeholder-gray-400',
|
||||
'h-12',
|
||||
props.borderless ? 'border-0' : 'border',
|
||||
props.transparent ? 'bg-transparent' : 'bg-white dark:bg-slate-800',
|
||||
// props.isReadOnly ? 'bg-gray-50 dark:bg-slate-600' : 'bg-white dark:bg-slate-800',
|
||||
];
|
||||
// if (props.icon) {
|
||||
base.push('pl-10');
|
||||
// }
|
||||
return base;
|
||||
});
|
||||
|
||||
let search = ref('');
|
||||
let data = reactive({
|
||||
search: search,
|
||||
isOpen: false,
|
||||
results: [],
|
||||
});
|
||||
let error = ref('');
|
||||
let selectedIndex: Ref<number> = ref(0);
|
||||
// const listItem = ref(null);
|
||||
const ul: Ref<Array<HTMLLIElement | null>> = ref([]);
|
||||
|
||||
watch(selectedIndex, (selectedIndex) => {
|
||||
if (selectedIndex != null && ul.value != null) {
|
||||
const currentElement: HTMLLIElement | null = ul.value[selectedIndex];
|
||||
currentElement &&
|
||||
currentElement.scrollIntoView({
|
||||
behavior: 'smooth',
|
||||
block: 'nearest',
|
||||
inline: 'start',
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
watch(search, async () => {
|
||||
await onChange();
|
||||
});
|
||||
|
||||
// function clear() {
|
||||
// data.search = "";
|
||||
// data.isOpen = false;
|
||||
// data.results = [];
|
||||
// error.value = "";
|
||||
// // this.$emit("clear");
|
||||
// }
|
||||
|
||||
// function onChange() {
|
||||
// if (!props.source || !data.search) {
|
||||
// return false;
|
||||
// }
|
||||
|
||||
// data.isOpen = true;
|
||||
// arrayLikeSearch(props.source);
|
||||
// }
|
||||
async function onChange() {
|
||||
if (!props.source || !data.search) return false;
|
||||
|
||||
selectedIndex.value = 0;
|
||||
|
||||
if (data.search.length >= 2) {
|
||||
data.isOpen = true;
|
||||
switch (true) {
|
||||
case typeof props.source === 'string':
|
||||
return await request(props.source, data.search);
|
||||
// case typeof props.source === 'function':
|
||||
// return props.source(data.search).then((response) => {
|
||||
// data.results = getResults(response);
|
||||
// });
|
||||
case Array.isArray(props.source):
|
||||
return arrayLikeSearch(props.source);
|
||||
default:
|
||||
throw new Error('typeof source is ' + typeof props.source);
|
||||
}
|
||||
} else {
|
||||
data.results = [];
|
||||
data.isOpen = false;
|
||||
}
|
||||
}
|
||||
|
||||
function getResults(response) {
|
||||
// if (props.responseProperty) {
|
||||
// let foundObj;
|
||||
// JSON.stringify(response, (_, nestedValue) => {
|
||||
// if (nestedValue && nestedValue[props.responseProperty]) foundObj = nestedValue[props.responseProperty];
|
||||
|
||||
// return nestedValue;
|
||||
// });
|
||||
// return foundObj;
|
||||
// }
|
||||
if (Array.isArray(response)) {
|
||||
return response;
|
||||
}
|
||||
return [];
|
||||
}
|
||||
|
||||
// function setResult(result) {
|
||||
// data.search = result[props.label];
|
||||
// data.isOpen = false;
|
||||
// }
|
||||
|
||||
// function request(url) {
|
||||
// return axios.get(url).then((response) => {
|
||||
// data.results = getResults(response);
|
||||
// });
|
||||
// }
|
||||
async function request(url, param) {
|
||||
try {
|
||||
let response = await searchTerm(url, param);
|
||||
error.value = '';
|
||||
data.results = getResults(response);
|
||||
// this.results = res.data;
|
||||
// this.loading = false;
|
||||
} catch (error) {
|
||||
error.value = error.message;
|
||||
// this.loading = false;
|
||||
}
|
||||
}
|
||||
|
||||
async function searchTerm(term: string, param): Promise<any> {
|
||||
let res = await axios.get(term, { params: { filter: param } });
|
||||
// console.log(res.data);
|
||||
return res.data; //.response;//.docs;
|
||||
}
|
||||
// async function request(term: string): Promise<any> {
|
||||
// let res = await axios.get('/api/persons', { params: { filter: term } });
|
||||
// return res.data; //.response;//.docs;
|
||||
// }
|
||||
|
||||
function arrayLikeSearch(items) {
|
||||
data.results = items.filter((item) => {
|
||||
return item.toLowerCase().indexOf(data.search.toLowerCase()) > -1;
|
||||
});
|
||||
}
|
||||
|
||||
function makeBold(suggestion) {
|
||||
const query = data.search.valueOf();
|
||||
const regex = new RegExp(query.split('').join('-?'), 'i');
|
||||
const test = suggestion.replace(regex, (match) => '<split>' + match + '<split>');
|
||||
// return suggestion.match(regex);
|
||||
// const splitWord = suggestion.match(regex);
|
||||
return test.split('<split>');
|
||||
}
|
||||
|
||||
function onArrowDown() {
|
||||
if (data.results.length > 0) {
|
||||
selectedIndex.value = selectedIndex.value === data.results.length - 1 ? 0 : selectedIndex.value + 1;
|
||||
// const currentElement: HTMLLIElement = ul.value[selectedIndex.value];
|
||||
}
|
||||
}
|
||||
|
||||
function onArrowUp() {
|
||||
if (data.results.length > 0) {
|
||||
selectedIndex.value = selectedIndex.value == 0 || selectedIndex.value == -1 ? data.results.length - 1 : selectedIndex.value - 1;
|
||||
}
|
||||
}
|
||||
</script>
|
|
@ -1,9 +1,9 @@
|
|||
<script setup>
|
||||
import { containerMaxW } from '@/config.js'
|
||||
import { containerMaxW } from '@/config.js';
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<section class="p-6" v-bind:class="containerMaxW">
|
||||
<slot />
|
||||
</section>
|
||||
<section class="p-6" v-bind:class="containerMaxW">
|
||||
<slot />
|
||||
</section>
|
||||
</template>
|
||||
|
|
Loading…
Add table
editor.link_modal.header
Reference in a new issue