v2 first commit
This commit is contained in:
77
resources/assets/js/components/Inputs/BaseCheckbox.vue
Normal file
77
resources/assets/js/components/Inputs/BaseCheckbox.vue
Normal file
@ -0,0 +1,77 @@
|
||||
<template>
|
||||
<div class="custom-control custom-checkbox"
|
||||
:class="[
|
||||
{disabled: disabled},
|
||||
{[`custom-checkbox-${type}`]: type},inlineClass]">
|
||||
<input :id="cbId"
|
||||
class="custom-control-input"
|
||||
:class="inputClasses"
|
||||
type="checkbox"
|
||||
:disabled="disabled"
|
||||
v-model="model"/>
|
||||
<label :for="cbId" class="custom-control-label">
|
||||
<slot>
|
||||
<span v-if="inline"> </span>
|
||||
</slot>
|
||||
</label>
|
||||
</div>
|
||||
</template>
|
||||
<script>
|
||||
export default {
|
||||
name: "base-checkbox",
|
||||
model: {
|
||||
prop: "checked"
|
||||
},
|
||||
props: {
|
||||
checked: {
|
||||
type: [Array, Boolean],
|
||||
description: "Whether checkbox is checked"
|
||||
},
|
||||
disabled: {
|
||||
type: Boolean,
|
||||
description: "Whether checkbox is disabled"
|
||||
},
|
||||
inline: {
|
||||
type: Boolean,
|
||||
description: "Whether checkbox is inline"
|
||||
},
|
||||
inputClasses: {
|
||||
type: [String, Object, Array],
|
||||
description: "Checkbox input classes"
|
||||
},
|
||||
type: {
|
||||
type: String,
|
||||
description: 'Checkbox type (e.g info, danger etc)'
|
||||
}
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
cbId: "",
|
||||
touched: false
|
||||
};
|
||||
},
|
||||
computed: {
|
||||
model: {
|
||||
get() {
|
||||
return this.checked;
|
||||
},
|
||||
set(check) {
|
||||
if (!this.touched) {
|
||||
this.touched = true;
|
||||
}
|
||||
this.$emit("input", check);
|
||||
}
|
||||
},
|
||||
inlineClass() {
|
||||
if (this.inline) {
|
||||
return `form-check-inline`;
|
||||
}
|
||||
}
|
||||
},
|
||||
created() {
|
||||
this.cbId = Math.random()
|
||||
.toString(16)
|
||||
.slice(2);
|
||||
}
|
||||
};
|
||||
</script>
|
176
resources/assets/js/components/Inputs/BaseInput.vue
Normal file
176
resources/assets/js/components/Inputs/BaseInput.vue
Normal file
@ -0,0 +1,176 @@
|
||||
<template>
|
||||
<div class="form-group" :class="[
|
||||
{'has-error': error},
|
||||
formClasses
|
||||
]">
|
||||
<slot name="label">
|
||||
<label v-if="label" :class="labelClasses">
|
||||
{{label}}
|
||||
</label>
|
||||
</slot>
|
||||
<div :class="[
|
||||
{'input-group input-group-merge': hasIcon},
|
||||
{'focused': focused},
|
||||
{'input-group-alternative': alternative},
|
||||
{'has-label': label || $slots.label},
|
||||
inputGroupClasses
|
||||
]">
|
||||
<div v-if="prependIcon || $slots.prepend" class="input-group-prepend">
|
||||
<span class="input-group-text">
|
||||
<slot name="prepend">
|
||||
<i :class="prependIcon"></i>
|
||||
</slot>
|
||||
</span>
|
||||
</div>
|
||||
<slot v-bind="slotData">
|
||||
<input
|
||||
:value="value"
|
||||
:type="type"
|
||||
v-on="listeners"
|
||||
v-bind="$attrs"
|
||||
:valid="!error"
|
||||
:required="required"
|
||||
class="form-control"
|
||||
:class="[{'is-valid': valid === true}, {'is-invalid': error}, inputClasses]">
|
||||
</slot>
|
||||
<div v-if="appendIcon || $slots.append" class="input-group-append">
|
||||
<span class="input-group-text">
|
||||
<slot name="append">
|
||||
<i :class="appendIcon"></i>
|
||||
</slot>
|
||||
</span>
|
||||
</div>
|
||||
<slot name="infoBlock"></slot>
|
||||
<slot name="error">
|
||||
<div v-if="error" class="invalid-feedback d-block"
|
||||
v-html="error">
|
||||
</div>
|
||||
</slot>
|
||||
<slot name="success">
|
||||
<div class="valid-feedback" v-if="!error && valid">
|
||||
{{successMessage}}
|
||||
</div>
|
||||
</slot>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
<script>
|
||||
export default {
|
||||
inheritAttrs: false,
|
||||
name: "base-input",
|
||||
props: {
|
||||
required: {
|
||||
type: Boolean,
|
||||
description: "Whether input is required (adds an asterix *)"
|
||||
},
|
||||
group: {
|
||||
type: Boolean,
|
||||
description: "Whether input is an input group (manual override in special cases)"
|
||||
},
|
||||
valid: {
|
||||
type: Boolean,
|
||||
description: "Whether is valid",
|
||||
default: undefined
|
||||
},
|
||||
alternative: {
|
||||
type: Boolean,
|
||||
description: "Whether input is of alternative layout"
|
||||
},
|
||||
label: {
|
||||
type: String,
|
||||
description: "Input label (text before input)"
|
||||
},
|
||||
error: {
|
||||
type: String,
|
||||
description: "Input error (below input)"
|
||||
},
|
||||
successMessage: {
|
||||
type: String,
|
||||
description: "Input success message",
|
||||
default: 'Looks good!'
|
||||
},
|
||||
formClasses: {
|
||||
type: String,
|
||||
description: "Input form css classes"
|
||||
},
|
||||
labelClasses: {
|
||||
type: String,
|
||||
description: "Input label css classes",
|
||||
default: "form-control-label"
|
||||
},
|
||||
inputClasses: {
|
||||
type: String,
|
||||
description: "Input css classes"
|
||||
},
|
||||
inputGroupClasses: {
|
||||
type: String,
|
||||
description: "Input group css classes"
|
||||
},
|
||||
value: {
|
||||
type: [String, Number],
|
||||
description: "Input value"
|
||||
},
|
||||
type: {
|
||||
type: String,
|
||||
description: "Input type",
|
||||
default: "text"
|
||||
},
|
||||
appendIcon: {
|
||||
type: String,
|
||||
description: "Append icon (right)"
|
||||
},
|
||||
prependIcon: {
|
||||
type: String,
|
||||
description: "Prepend icon (left)"
|
||||
}
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
focused: false
|
||||
};
|
||||
},
|
||||
computed: {
|
||||
listeners() {
|
||||
return {
|
||||
...this.$listeners,
|
||||
input: this.updateValue,
|
||||
focus: this.onFocus,
|
||||
blur: this.onBlur
|
||||
};
|
||||
},
|
||||
slotData() {
|
||||
return {
|
||||
focused: this.focused,
|
||||
error: this.error,
|
||||
...this.listeners
|
||||
};
|
||||
},
|
||||
hasIcon() {
|
||||
const { append, prepend } = this.$slots;
|
||||
return (
|
||||
append !== undefined ||
|
||||
prepend !== undefined ||
|
||||
this.appendIcon !== undefined ||
|
||||
this.prependIcon !== undefined ||
|
||||
this.group
|
||||
);
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
updateValue(evt) {
|
||||
let value = evt.target.value;
|
||||
this.$emit("input", value);
|
||||
},
|
||||
onFocus(evt) {
|
||||
this.focused = true;
|
||||
this.$emit("focus", evt);
|
||||
},
|
||||
onBlur(evt) {
|
||||
this.focused = false;
|
||||
this.$emit("blur", evt);
|
||||
}
|
||||
}
|
||||
};
|
||||
</script>
|
||||
<style>
|
||||
</style>
|
68
resources/assets/js/components/Inputs/BaseRadio.vue
Normal file
68
resources/assets/js/components/Inputs/BaseRadio.vue
Normal file
@ -0,0 +1,68 @@
|
||||
<template>
|
||||
<div
|
||||
class="custom-control custom-radio"
|
||||
:class="[inlineClass, { disabled: disabled }]">
|
||||
<input
|
||||
:id="cbId"
|
||||
class="custom-control-input"
|
||||
type="radio"
|
||||
:disabled="disabled"
|
||||
:value="name"
|
||||
v-model="model"
|
||||
/>
|
||||
<label :for="cbId" class="custom-control-label">
|
||||
<slot>
|
||||
<span v-if="inline"> </span>
|
||||
</slot>
|
||||
</label>
|
||||
</div>
|
||||
</template>
|
||||
<script>
|
||||
export default {
|
||||
name: 'base-radio',
|
||||
props: {
|
||||
name: {
|
||||
type: [String, Number],
|
||||
description: 'Radio label'
|
||||
},
|
||||
disabled: {
|
||||
type: Boolean,
|
||||
description: 'Whether radio is disabled'
|
||||
},
|
||||
value: {
|
||||
type: [String, Boolean],
|
||||
description: 'Radio value'
|
||||
},
|
||||
inline: {
|
||||
type: Boolean,
|
||||
description: 'Whether radio is inline'
|
||||
}
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
cbId: ''
|
||||
};
|
||||
},
|
||||
computed: {
|
||||
model: {
|
||||
get() {
|
||||
return this.value;
|
||||
},
|
||||
set(value) {
|
||||
this.$emit('input', value);
|
||||
}
|
||||
},
|
||||
inlineClass() {
|
||||
if (this.inline) {
|
||||
return `form-check-inline`;
|
||||
}
|
||||
return '';
|
||||
}
|
||||
},
|
||||
created() {
|
||||
this.cbId = Math.random()
|
||||
.toString(16)
|
||||
.slice(2);
|
||||
}
|
||||
};
|
||||
</script>
|
125
resources/assets/js/components/Inputs/DropzoneFileUpload.vue
Normal file
125
resources/assets/js/components/Inputs/DropzoneFileUpload.vue
Normal file
@ -0,0 +1,125 @@
|
||||
<template>
|
||||
<div class="dropzone mb-3 dz-clickable"
|
||||
:class="[multiple ? 'dropzone-multiple': 'dropzone-single']">
|
||||
<div class="fallback">
|
||||
<div class="custom-file">
|
||||
<input type="file"
|
||||
class="custom-file-input"
|
||||
id="projectCoverUploads"
|
||||
:multiple="multiple">
|
||||
<label class="custom-file-label" for="projectCoverUploads">Choose file</label>
|
||||
</div>
|
||||
</div>
|
||||
<div class="dz-preview dz-preview-single"
|
||||
v-if="!multiple"
|
||||
:class="previewClasses"
|
||||
ref="previewSingle">
|
||||
<div class="dz-preview-cover">
|
||||
<img class="dz-preview-img" data-dz-thumbnail>
|
||||
</div>
|
||||
</div>
|
||||
<ul v-else
|
||||
class="dz-preview dz-preview-multiple list-group list-group-lg list-group-flush"
|
||||
:class="previewClasses"
|
||||
ref="previewMultiple">
|
||||
<li class="list-group-item px-0">
|
||||
<div class="row align-items-center">
|
||||
<div class="col-auto">
|
||||
<div class="avatar">
|
||||
<img class="avatar-img rounded" data-dz-thumbnail>
|
||||
</div>
|
||||
</div>
|
||||
<div class="col ml--3">
|
||||
<h4 class="mb-1" data-dz-name>...</h4>
|
||||
<p class="small text-muted mb-0" data-dz-size>...</p>
|
||||
</div>
|
||||
<div class="col-auto">
|
||||
<button data-dz-remove="true" class="btn btn-danger btn-sm">
|
||||
<i class="fas fa-trash"></i>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
</template>
|
||||
<script>
|
||||
export default {
|
||||
name: 'dropzone-file-upload',
|
||||
props: {
|
||||
options: {
|
||||
type: Object,
|
||||
default: () => ({})
|
||||
},
|
||||
value: [String, Object, Array],
|
||||
url: {
|
||||
type: String,
|
||||
default: 'http://'
|
||||
},
|
||||
multiple: Boolean,
|
||||
previewClasses: [String, Object, Array]
|
||||
},
|
||||
model: {
|
||||
prop: 'value',
|
||||
event: 'change'
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
currentFile: null,
|
||||
files: [],
|
||||
showList: false,
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
async initDropzone() {
|
||||
let Dropzone = await import('dropzone')
|
||||
Dropzone = Dropzone.default || Dropzone
|
||||
Dropzone.autoDiscover = false
|
||||
let preview = this.multiple ? this.$refs.previewMultiple : this.$refs.previewSingle;
|
||||
let self = this
|
||||
let finalOptions = {
|
||||
...this.options,
|
||||
url: this.url,
|
||||
thumbnailWidth: null,
|
||||
thumbnailHeight: null,
|
||||
previewsContainer: preview,
|
||||
previewTemplate: preview.innerHTML,
|
||||
maxFiles: (!this.multiple) ? 1 : null,
|
||||
acceptedFiles: (!this.multiple) ? 'image/*' : null,
|
||||
init: function () {
|
||||
this.on("addedfile", function (file) {
|
||||
if (!self.multiple && self.currentFile) {
|
||||
// this.removeFile(this.currentFile);
|
||||
}
|
||||
self.currentFile = file;
|
||||
})
|
||||
}
|
||||
}
|
||||
this.dropzone = new Dropzone(this.$el, finalOptions)
|
||||
preview.innerHTML = ''
|
||||
let evtList = ['drop', 'dragstart', 'dragend', 'dragenter', 'dragover', 'addedfile', 'removedfile', 'thumbnail', 'error', 'processing', 'uploadprogress', 'sending', 'success', 'complete', 'canceled', 'maxfilesreached', 'maxfilesexceeded', 'processingmultiple', 'sendingmultiple', 'successmultiple', 'completemultiple', 'canceledmultiple', 'totaluploadprogress', 'reset', 'queuecomplete']
|
||||
evtList.forEach(evt => {
|
||||
this.dropzone.on(evt, (data) => {
|
||||
this.$emit(evt, data);
|
||||
|
||||
if (evt === 'addedfile') {
|
||||
this.files.push(data)
|
||||
this.$emit('change', this.files);
|
||||
} else if (evt === 'removedfile') {
|
||||
let index = this.files.findIndex(f => f.upload.uuid === data.upload.uuid)
|
||||
if (index !== -1) {
|
||||
this.files.splice(index, 1);
|
||||
}
|
||||
this.$emit('change', this.files);
|
||||
}
|
||||
})
|
||||
})
|
||||
}
|
||||
},
|
||||
async mounted() {
|
||||
this.initDropzone()
|
||||
}
|
||||
}
|
||||
</script>
|
||||
<style>
|
||||
</style>
|
54
resources/assets/js/components/Inputs/FileInput.vue
Normal file
54
resources/assets/js/components/Inputs/FileInput.vue
Normal file
@ -0,0 +1,54 @@
|
||||
<template>
|
||||
<div class="custom-file">
|
||||
<input type="file"
|
||||
class="custom-file-input"
|
||||
id="customFileLang"
|
||||
lang="en"
|
||||
v-bind="$attrs"
|
||||
v-on="listeners"
|
||||
/>
|
||||
<label class="custom-file-label" for="customFileLang">
|
||||
{{label}}
|
||||
</label>
|
||||
</div>
|
||||
</template>
|
||||
<script>
|
||||
export default {
|
||||
name: 'file-input',
|
||||
inheritAttrs: false,
|
||||
props: {
|
||||
initialLabel: {
|
||||
type: String,
|
||||
default: 'Select file'
|
||||
}
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
files: []
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
listeners() {
|
||||
return {
|
||||
...this.$listeners,
|
||||
change: this.fileChange
|
||||
}
|
||||
},
|
||||
label() {
|
||||
let fileNames = [];
|
||||
for (let file of this.files) {
|
||||
fileNames.push(file.name)
|
||||
}
|
||||
return fileNames.length ? fileNames.join(', ') : this.initialLabel
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
fileChange(evt) {
|
||||
this.files = evt.target.files
|
||||
this.$emit('change', this.files)
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
<style>
|
||||
</style>
|
96
resources/assets/js/components/Inputs/HtmlEditor.vue
Normal file
96
resources/assets/js/components/Inputs/HtmlEditor.vue
Normal file
@ -0,0 +1,96 @@
|
||||
<template>
|
||||
<div class="quill">
|
||||
<div :id="toolbarId">
|
||||
<div class="ql-formats">
|
||||
<button class="ql-bold"></button>
|
||||
<button class="ql-italic"></button>
|
||||
<button class="ql-underline"></button>
|
||||
<button class="ql-link"></button>
|
||||
<button class="ql-blockquote"></button>
|
||||
<button class="ql-code"></button>
|
||||
<button class="ql-image"></button>
|
||||
<button type="button" class="ql-list" value="ordered"></button>
|
||||
<button type="button" class="ql-list" value="bullet"></button>
|
||||
</div>
|
||||
</div>
|
||||
<div :id="editorId" :name="name" class="" ref="editor">
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
<script>
|
||||
export default {
|
||||
name: 'html-editor',
|
||||
props: {
|
||||
value: {
|
||||
type: String,
|
||||
default: ''
|
||||
},
|
||||
name: String
|
||||
},
|
||||
data () {
|
||||
return {
|
||||
editor: null,
|
||||
content: null,
|
||||
lastHtmlValue: '',
|
||||
editorId: null,
|
||||
toolbarId: null
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
initialize (Quill) {
|
||||
this.editor = new Quill(`#${this.editorId}`, {
|
||||
theme: 'snow',
|
||||
modules: {
|
||||
toolbar: `#${this.toolbarId}`
|
||||
}
|
||||
})
|
||||
|
||||
if (this.value.length > 0) {
|
||||
this.editor.pasteHTML(this.value)
|
||||
}
|
||||
|
||||
let editorRef = this.$refs.editor;
|
||||
let node = editorRef.children[0];
|
||||
this.editor.on('text-change', () => {
|
||||
let html = node.innerHTML
|
||||
if (html === '<p><br></p>') {
|
||||
html = '';
|
||||
}
|
||||
this.content = html
|
||||
this.$emit('input', this.content);
|
||||
})
|
||||
},
|
||||
pasteHTML () {
|
||||
if (!this.editor) {
|
||||
return
|
||||
}
|
||||
this.editor.pasteHTML(this.value)
|
||||
},
|
||||
randomString() {
|
||||
let text = "";
|
||||
let possible = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz";
|
||||
|
||||
for (let i = 0; i < 5; i++)
|
||||
text += possible.charAt(Math.floor(Math.random() * possible.length));
|
||||
|
||||
return text;
|
||||
}
|
||||
},
|
||||
async mounted () {
|
||||
let Quill = await import('quill')
|
||||
Quill = Quill.default || Quill
|
||||
this.editorId = this.randomString();
|
||||
this.toolbarId = this.randomString();
|
||||
this.$nextTick(() => {
|
||||
this.initialize(Quill)
|
||||
});
|
||||
},
|
||||
watch: {
|
||||
value (newVal) {
|
||||
if (newVal !== this.content) {
|
||||
this.pasteHTML(newVal);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
45
resources/assets/js/components/Inputs/IconCheckbox.vue
Normal file
45
resources/assets/js/components/Inputs/IconCheckbox.vue
Normal file
@ -0,0 +1,45 @@
|
||||
<template>
|
||||
<div
|
||||
class="choice"
|
||||
:class="{ active: checked }"
|
||||
data-toggle="wizard-checkbox"
|
||||
@click="updateValue"
|
||||
>
|
||||
<input
|
||||
type="checkbox"
|
||||
:name="name"
|
||||
:disabled="disabled"
|
||||
:checked="checked"
|
||||
/>
|
||||
<div class="icon">
|
||||
<slot name="icon"> <i :class="icon"></i> </slot>
|
||||
</div>
|
||||
<slot name="title">
|
||||
<h6>{{ title }}</h6>
|
||||
</slot>
|
||||
</div>
|
||||
</template>
|
||||
<script>
|
||||
export default {
|
||||
name: 'icon-checkbox',
|
||||
model: {
|
||||
prop: 'checked'
|
||||
},
|
||||
props: {
|
||||
checked: {
|
||||
type: Boolean,
|
||||
default: false
|
||||
},
|
||||
name: String,
|
||||
title: String,
|
||||
icon: String,
|
||||
disabled: Boolean
|
||||
},
|
||||
methods: {
|
||||
updateValue() {
|
||||
this.$emit('input', !this.checked);
|
||||
}
|
||||
}
|
||||
};
|
||||
</script>
|
||||
<style></style>
|
95
resources/assets/js/components/Inputs/TagsInput.vue
Normal file
95
resources/assets/js/components/Inputs/TagsInput.vue
Normal file
@ -0,0 +1,95 @@
|
||||
<template>
|
||||
<div class="tags-input__wrapper">
|
||||
<el-tag
|
||||
v-for="(tag, index) in dynamicTags"
|
||||
:key="tag + index"
|
||||
size="small"
|
||||
:type="tagType"
|
||||
:closable="true"
|
||||
:close-transition="false"
|
||||
@close="handleClose(tag)"
|
||||
>
|
||||
{{ tag }}
|
||||
</el-tag>
|
||||
|
||||
<input
|
||||
type="text"
|
||||
placeholder="Add new tag"
|
||||
class="form-control"
|
||||
v-model="inputValue"
|
||||
ref="saveTagInput"
|
||||
size="mini"
|
||||
@input="onInput"
|
||||
@keyup.enter="handleInputConfirm"
|
||||
@blur="handleInputConfirm"
|
||||
/>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import { Tag } from 'element-ui';
|
||||
|
||||
export default {
|
||||
name: 'tags-input',
|
||||
components: {
|
||||
[Tag.name]: Tag
|
||||
},
|
||||
props: {
|
||||
value: {
|
||||
type: Array,
|
||||
default: () => [],
|
||||
description: 'List of tags'
|
||||
},
|
||||
tagType: {
|
||||
type: String,
|
||||
default: 'primary',
|
||||
description: 'Tag type (primary|danger etc)'
|
||||
}
|
||||
},
|
||||
model: {
|
||||
prop: 'value',
|
||||
event: 'change'
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
dynamicTags: [],
|
||||
inputVisible: false,
|
||||
inputValue: ''
|
||||
};
|
||||
},
|
||||
methods: {
|
||||
handleClose(tag) {
|
||||
this.dynamicTags.splice(this.dynamicTags.indexOf(tag), 1);
|
||||
this.$emit('change', this.dynamicTags);
|
||||
},
|
||||
showInput() {
|
||||
this.inputVisible = true;
|
||||
this.$nextTick(() => {
|
||||
this.$refs.saveTagInput.$refs.input.focus();
|
||||
});
|
||||
},
|
||||
|
||||
handleInputConfirm() {
|
||||
let inputValue = this.inputValue;
|
||||
if (inputValue) {
|
||||
this.dynamicTags.push(inputValue);
|
||||
this.$emit('change', this.dynamicTags);
|
||||
}
|
||||
this.inputVisible = false;
|
||||
this.inputValue = '';
|
||||
},
|
||||
onInput(evt) {
|
||||
this.$emit('input', evt.target.value);
|
||||
}
|
||||
},
|
||||
created() {
|
||||
this.$watch(
|
||||
'value',
|
||||
newVal => {
|
||||
this.dynamicTags = [...newVal];
|
||||
},
|
||||
{ immediate: true }
|
||||
);
|
||||
}
|
||||
};
|
||||
</script>
|
Reference in New Issue
Block a user