user UI setup

user login UI
This commit is contained in:
glovetleong
2021-07-06 11:54:00 +08:00
parent d843a1d970
commit ad85832e75
347 changed files with 59787 additions and 10 deletions
+21
View File
@@ -0,0 +1,21 @@
<template>
<div id="app">
<component :is="layout">
<transition name="fade" mode="out-in">
<router-view></router-view>
</transition>
</component>
</div>
</template>
<script>
const default_layout = "default";
export default {
computed: {
layout() {
let vm = this;
return (vm.$route.meta.layout || default_layout) + '-layout';
}
}
}
</script>
+112
View File
@@ -0,0 +1,112 @@
window._ = require('lodash');
// Axios
window.axios = require('axios');
window.axios.defaults.headers.common['X-Requested-With'] = 'XMLHttpRequest';
// Token
let token = document.head.querySelector('meta[name="csrf-token"]');
if (token) {
window.axios.defaults.headers.common['X-CSRF-TOKEN'] = token.content;
} else {
console.error('CSRF token not found: https://laravel.com/docs/csrf#csrf-x-csrf-token');
}
// Vuetify
import Vuetify, {
VApp,
VTextField,
VBtn,
VBreadcrumbs,
VSwitch,
VRadioGroup,
VRadio
} from 'vuetify/lib';
// import 'material-design-icons-iconfont/dist/material-design-icons.css';
import 'vuetify/src/stylus/app.styl';
Vue.use(Vuetify, {
components: {
VApp,
VTextField,
VBtn,
VBreadcrumbs,
VSwitch,
VRadioGroup,
VRadio
},
options: {
customProperties: true
},
iconfont: 'md' || 'mdi',
});
// Vue Meta
import VueMeta from 'vue-meta'
Vue.use(VueMeta)
// Boostrap
import BootstrapVue from 'bootstrap-vue'
Vue.use(BootstrapVue);
// Form Wiziard
import VueFormWizard from 'vue-form-wizard'
import 'vue-form-wizard/dist/vue-form-wizard.min.css'
Vue.use(VueFormWizard);
// Tags Input
import VueTagsInput from '@johmun/vue-tags-input';
Vue.component('vue-tags-input', VueTagsInput);
// Toasted
import Toasted from 'vue-toasted';
let Options = {
// iconPack : 'fontawesome'
}
Vue.use(Toasted, Options);
// Sprintf
window.Sprintf = require('sprintf-js').sprintf;
// Youtube
import VueYouTubeEmbed from 'vue-youtube-embed';
Vue.use(VueYouTubeEmbed);
// VueElementLoading
import VueElementLoading from 'vue-element-loading';
Vue.component('vue-element-loading', VueElementLoading);
//Utilities
import Form from './utilities/form';
window.Form = Form;
import Constants from './utilities/constants';
window.Constants = Constants;
window.General = require('./utilities/general');
window.NavigationAccess = require('./utilities/navigationAccess');
import En from './langs/en';
window.En = En;
import ENV from './utilities/env';
window.ENV = ENV;
/**
* Echo exposes an expressive API for subscribing to channels and listening
* for events that are broadcast by Laravel. Echo and event broadcasting
* allows your team to easily build robust real-time web applications.
*/
// import Echo from 'laravel-echo';
// window.Pusher = require('pusher-js');
// window.Echo = new Echo({
// broadcaster: 'pusher',
// key: process.env.MIX_PUSHER_APP_KEY,
// cluster: process.env.MIX_PUSHER_APP_CLUSTER,
// forceTLS: true
// });
@@ -0,0 +1,45 @@
<template>
<div >
<label v-if="label" for="name">{{ label }}</label>
<div class="form-group" :class="{'has-error': form.errors.has(input_name)}">
<datepicker
:name="input_name"
input-class="form-control"
:placeholder="placeholder"
v-model="form[input_name]"
@input="form.errors.clear(input_name)"
format="dd-MM-yy"
:disabled-dates="state.disabledDates"
></datepicker>
<div class="text-danger text-left" v-if="form.errors.has(input_name)" v-text="form.errors.get(input_name)"></div>
</div>
</div>
</template>
<script type="text/javascript">
import Datepicker from 'vuejs-datepicker';
import moment from 'moment';
export default {
props: ['input_name', 'form', 'placeholder', 'label', 'disable_pass_date'],
components: {
Datepicker
},
data() {
let vm = this;
return {
state : {
disabledDates: {
customPredictor: function(date) {
if (!vm.disable_pass_date) {
if(date < moment().startOf('day') ){
return true
}
}
}
}
}
}
}
}
</script>
@@ -0,0 +1,63 @@
<template>
<div >
<label v-if="label" for="name">{{ label }}</label>
<div class="row">
<div class="col-6">
<div class="form-group" :class="{'has-error': form.errors.has(input_name_1)}">
<datepicker
:name="input_name_1"
input-class="form-control"
:placeholder="placeholder"
v-model="form[input_name_1]"
@input="form.errors.clear(input_name_1)"
format="dd-MM-yy"
:disabled-dates="state.disabledDates"
></datepicker>
<div class="text-danger text-left" v-if="form.errors.has(input_name_1)" v-text="form.errors.get(input_name_1)"></div>
</div>
</div>
<div class="col-6">
<div class="form-group" :class="{'has-error': form.errors.has(input_name_2)}">
<datepicker
:name="input_name_2"
input-class="form-control"
:placeholder="placeholder"
v-model="form[input_name_2]"
@input="form.errors.clear(input_name_2)"
format="dd-MM-yy"
:disabled-dates="state.disabledDates"
></datepicker>
<div class="text-danger text-left" v-if="form.errors.has(input_name_2)" v-text="form.errors.get(input_name_2)"></div>
</div>
</div>
</div>
</div>
</template>
<script type="text/javascript">
import Datepicker from 'vuejs-datepicker';
import moment from 'moment';
export default {
props: ['input_name_1', 'input_name_2', 'form', 'placeholder', 'label', 'disable_pass_date'],
components: {
Datepicker
},
data() {
let vm = this;
return {
state : {
disabledDates: {
customPredictor: function(date) {
if (!vm.disable_pass_date) {
if(date < moment().startOf('day') ){
return true
}
}
}
}
}
}
}
}
</script>
@@ -0,0 +1,32 @@
<template>
<div class="form-group">
<label v-if="label" for="name">{{ label }}</label>
<div class="row">
<div class="col-6">
<div class="form-group">
<input type="text"
class="form-control"
v-model="form[input_name_1]"
:readonly="true"
>
</div>
</div>
<div class="col-6">
<div class="form-group">
<input type="text"
class="form-control"
v-model="form[input_name_2]"
:readonly="true"
>
</div>
</div>
</div>
</div>
</template>
<script type="text/javascript">
export default {
props: ['input_name_1', 'input_name_2', 'form', 'label', 'disable_pass_date'],
}
</script>
@@ -0,0 +1,52 @@
<template>
<div >
<label v-if="label" for="name">{{ label }}</label>
<div class="row">
<div class="col-6">
<div class="form-group" :class="{'has-error': form.errors.has(input_name_1)}">
<datetime
type="datetime"
class="form-control"
v-model="form[input_name_1]"
value-zone="Asia/Singapore"
zone="Asia/Singapore"
@input="form.errors.clear(input_name_1)"
></datetime>
<div class="text-danger text-left" v-if="form.errors.has(input_name_1)" v-text="form.errors.get(input_name_1)"></div>
</div>
</div>
<div class="col-6">
<div class="form-group" :class="{'has-error': form.errors.has(input_name_2)}">
<datetime
type="datetime"
class="form-control"
v-model="form[input_name_2]"
value-zone="Asia/Singapore"
zone="Asia/Singapore"
@input="form.errors.clear(input_name_2)"
></datetime>
<div class="text-danger text-left" v-if="form.errors.has(input_name_2)" v-text="form.errors.get(input_name_2)"></div>
</div>
</div>
</div>
</div>
</template>
<script type="text/javascript">
import { Datetime } from 'vue-datetime';
import 'vue-datetime/dist/vue-datetime.css';
import moment from 'moment';
export default {
props: ['input_name_1', 'input_name_2', 'form', 'placeholder', 'label', 'disable_pass_date'],
components: {
Datetime
},
data() {
let vm = this;
return {
}
}
}
</script>
@@ -0,0 +1,43 @@
<template>
<div class="form-group" :class="{'has-error': form.errors.has(input_name)}">
<label v-if="label" for="name">{{ label }}</label>
<div class="upload-box">
<div class="file-container mb-1">
<i class="fa fa-download"></i>
<input type='file'
:name="input_name"
@change="onChange"
/>
<div>{{ file_name }}</div>
</div>
</div>
<div class="text-danger text-left" v-if="form.errors.has(input_name)" v-text="form.errors.get(input_name)"></div>
</div>
</template>
<script>
export default {
props: ['input_name', 'form', 'placeholder', 'label'],
components: {
},
data() {
return {
file_name: '',
file: []
}
},
methods: {
onChange(e) {
let vm = this;
let files = e.target.files;
General.imageBase64(e.target.files).then(data => {
vm.file = data[0];
vm.file_name = e.target.files[0].name;
vm.form[vm.input_name] = [vm.file];
});
},
}
}
</script>
@@ -0,0 +1,89 @@
<template>
<div class="form-group" :class="{'has-error': form.errors.has(input_name)}">
<label v-if="label" for="name">{{ label }}</label>
<div class="upload-box">
<div class="file-container mb-1">
<i class="fa fa-download"></i>
<input type='file'
:name="input_name"
@change="onChange"
/>
</div>
<div v-if="preview" v-for="(img, $index) in preview" :key="$index">
<a v-if="mime_type == 'application/pdf'" class="btn btn-xs btn-light" :href="img" target="_blank">
<i class="fa fa-file-pdf"></i>
</a>
<div v-if="mime_type != 'application/pdf'">
<img class="img-fluid" alt='' :src="img"/>
</div>
</div>
</div>
<div class="text-danger text-left" v-if="form.errors.has(input_name)" v-text="form.errors.get(input_name)"></div>
<cropper-modal
:img="img"
@clicked="getCropImage"
>
</cropper-modal>
</div>
</template>
<script>
import CropperModal from "../modals/CropperModal";
export default {
props: {
input_name: String,
form: Object,
placeholder: String,
label: String,
exist_img: Array,
},
components: {
CropperModal
},
data() {
return {
preview: [],
img: [],
mime_type: '',
}
},
methods: {
onChange(e) {
let vm = this;
let files = e.target.files;
vm.mime_type = files[0].type;
General.imageBase64(e.target.files).then(data => {
vm.img = data[0];
vm.cropperModalToggle();
});
},
cropperModalToggle() {
let vm = this;
vm.$bvModal.show('cropper-modal');
},
getCropImage(img) {
let vm = this;
vm.form[vm.input_name] = [img];
vm.preview = [img];
},
loadExistingImg() {
let vm = this;
if (vm.exist_img[0]) {
vm.mime_type = vm.exist_img[0].mime_type;
Array.from(vm.exist_img).forEach(file => {
vm.preview.push(file.file_info.original.file);
});
}
}
},
watch: {
'exist_img': 'loadExistingImg',
},
created() {
let vm = this;
vm.loadExistingImg();
}
}
</script>
@@ -0,0 +1,46 @@
<template>
<div class="form-group">
<label v-if="label" for="name">{{ label }}</label>
<div class="upload-box">
<div v-if="preview" v-for="(img, $index) in preview" :key="$index">
<div>
<img class="img-fluid" alt='' :src="img"/>
</div>
</div>
</div>
</div>
</template>
<script>
export default {
props: {
label: String,
exist_img: Array,
},
data() {
return {
preview: [],
img: [],
mime_type: '',
}
},
methods: {
loadExistingImg() {
let vm = this;
if (vm.exist_img[0]) {
vm.mime_type = vm.exist_img[0].mime_type;
Array.from(vm.exist_img).forEach(file => {
vm.preview.push(file.file_info.original.file);
});
}
}
},
watch: {
'exist_img': 'loadExistingImg',
},
created() {
let vm = this;
vm.loadExistingImg();
}
}
</script>
@@ -0,0 +1,34 @@
<template>
<div class="form-group" :class="{'has-error': form.errors.has(input_name)}">
<label v-if="label" for="name">{{ label }}</label>
<div class="input-group">
<div class="input-group-prepend">
<span class="input-group-text">{{ icon }}</span>
</div>
<input type="text"
class="form-control"
:placeholder="placeholder"
v-model="form[input_name]"
@keydown="form.errors.clear(input_name)"
@keypress="isNumber($event)"
@input="$emit('clicked')"
:readonly="readonly"
>
</div>
<div class="text-danger text-left" v-if="form.errors.has(input_name)" v-text="form.errors.get(input_name)"></div>
</div>
</template>
<script type="text/javascript">
export default {
props: ['input_name', 'form', 'icon', 'placeholder', 'label', 'readonly'],
methods: {
isNumber($event) {
let keyCode = ($event.keyCode ? $event.keyCode : $event.which);
if ((keyCode < 48 || keyCode > 57) && keyCode !== 46) { // 46 is dot
$event.preventDefault();
}
}
}
}
</script>
@@ -0,0 +1,36 @@
<template>
<div class="form-group" :class="{'has-error': form.errors.has(input_name)}">
<label v-if="label" for="name">{{ label }}</label>
<input type="text"
:name="input_name"
class="form-control"
:placeholder="placeholder"
v-model="form[input_name]"
@keydown="form.errors.clear(input_name)"
@keypress="isNumber($event)"
@keyup="$emit('clicked')"
:readonly="readonly"
>
<div class="text-danger text-left" v-if="form.errors.has(input_name)" v-text="form.errors.get(input_name)"></div>
</div>
</template>
<script type="text/javascript">
export default {
props: {
input_name: String,
form: Object,
placeholder: String,
label: String,
readonly: Boolean,
},
methods: {
isNumber($event) {
let keyCode = ($event.keyCode ? $event.keyCode : $event.which);
if ((keyCode < 48 || keyCode > 57) && keyCode !== 46) { // 46 is dot
$event.preventDefault();
}
}
}
}
</script>
@@ -0,0 +1,44 @@
<template>
<div class="form-group" :class="{'has-error': form.errors.has(input_name)}">
<label v-if="label" for="name">{{ label }}</label>
<div class="input-group">
<input :type="password_type ? 'password' : 'text'"
class="form-control"
:placeholder="placeholder"
v-model="form[input_name]"
@keydown="form.errors.clear(input_name)"
>
<div class="input-group-prepend pointer-cursor" @click="passwordToggle">
<span class="input-group-text">
<i :class="password_type ? 'fa fa-eye-slash' : 'fa fa-eye'" aria-hidden="true"></i>
</span>
</div>
</div>
<div class="text-danger text-left" v-if="form.errors.has(input_name)" v-text="form.errors.get(input_name)"></div>
</div>
</template>
<script type="text/javascript">
export default {
props: {
input_name: String,
form: Object,
placeholder: String,
label: String
},
data() {
let vm = this;
return {
password_type: true
}
},
methods: {
passwordToggle() {
let vm = this;
vm.password_type = !vm.password_type;
}
}
}
</script>
@@ -0,0 +1,27 @@
<template>
<div class="form-group" :class="{'has-error': form.errors.has(input_name)}">
<div>
<label v-if="label" for="name">{{ label }}</label>
</div>
<v-radio-group v-model="form[input_name]" :mandatory="false">
<v-radio
v-if="list" v-for="(item, $index) in list" :key="$index"
color="primary"
:label="item.name"
:value="item.id"
></v-radio>
</v-radio-group>
<div class="text-danger text-left" v-if="form.errors.has(input_name)" v-text="form.errors.get(input_name)"></div>
</div>
</template>
<script type="text/javascript">
export default {
props: {
input_name: String,
form: Object,
list: Array,
label: String,
}
}
</script>
@@ -0,0 +1,140 @@
<template>
<div class="form-group" :class="{'has-error': form.errors.has(input_name)}">
<label v-if="label" for="name">{{ label }}</label>
<vue-tags-input
ref="tags_input"
v-model="tag"
:tags="tags"
:autocomplete-min-length="0"
:add-on-blur="false"
:autocomplete-items="autocompleteItems"
:before-deleting-tag="deleteTag"
:max-tags="limit_tag"
:add-only-from-autocomplete="!can_add_new_tag ? true : false"
:placeholder="''"
@tags-changed="update"
@before-adding-tag="checkTag"
@focus="focus()"
>
</vue-tags-input>
<div class="text-danger text-left" v-if="form.errors.has(input_name)" v-text="form.errors.get(input_name)"></div>
</div>
</template>
<script type="text/javascript">
export default {
props: {
input_name: String,
form: Object,
exist_data: Array,
data_set: Array,
placeholder: String,
label: String,
endpoint: String,
params: Object,
search_field: String,
is_array: Boolean,
limit_tag: Number,
return_key: String,
can_add_new_tag: Boolean
},
data() {
let vm = this;
return {
tag: '',
original_tag: [],
tags: vm.exist_data ? vm.exist_data : [],
autocompleteItems: [],
pervious_item: []
}
},
methods: {
focus() {
let vm = this;
if (vm.pervious_item.length > 0) {
vm.pervious_item = [];
vm.initItems();
}
},
checkTag(obj) {
let vm = this;
if (vm.limit_tag >= vm.tags.length) {
vm.tags = [];
}
obj.addTag();
},
update(newTags = '') {
let vm = this;
vm.tags = newTags;
vm.pervious_item = vm.autocompleteItems;
vm.autocompleteItems = [];
vm.keyConvert();
vm.$emit('clicked', vm.extractRow(vm.tags));
vm.form.errors.clear(vm.input_name);
},
deleteTag() {
vm.$emit('clicked', vm.extractRow(vm.tags));
},
keyConvert() {
let vm = this;
if (!vm.original) {
vm.form[vm.input_name] = General.getKey(vm.tags, (!vm.return_key ? 'id' : vm.return_key));
}
else {
vm.form[vm.input_name] = vm.original_tag;
}
},
initItems() {
let vm = this;
if (vm.pervious_item.length > 0) {
return;
}
if (!vm.data_set) {
let search_field = {};
search_field[vm.search_field] = vm.array ? [vm.tag] : vm.tag;
let form = new Form({...vm.params, ...search_field});
General.getCall(vm.endpoint, form).then(data => {
vm.autocompleteItems = data.data.map(item => {
vm.original_tag.push(vm.tag);
return {
id: item.id,
hash_id: (item.hash_id ? item.hash_id : ''),
row: item,
text: (item.name ? item.name : item.hash_id )
};
});
});
}
else {
vm.autocompleteItems = vm.data_set;
}
},
extractRow() {
let vm = this;
let return_row = [];
vm.tags.forEach(function(element, index) {
return_row.push(element.row);
});
return return_row;
}
},
created() {
let vm = this;
setTimeout(function(){
vm.tags = vm.exist_data ? vm.exist_data : [];
vm.$emit('clicked', vm.extractRow(vm.tags));
}, 1000);
vm.initItems();
},
watch: {
'tag': 'initItems',
}
}
</script>
@@ -0,0 +1,23 @@
<template>
<div class="form-group" :class="{'has-error': form.errors.has(input_name)}">
<label v-if="label">{{ label }}</label>
<select
class="form-control"
v-model="form[input_name]"
@change="$emit('clicked')"
:disabled="readonly"
>
<option disabled value="">Select Option</option>
<option :value="option.id" v-if="list" v-for="(option, $index) in list" :key="$index">
{{ option.name }}
</option>
</select>
<div class="text-danger text-left" v-if="form.errors.has(input_name)" v-text="form.errors.get(input_name)"></div>
</div>
</template>
<script type="text/javascript">
export default {
props: ['input_name', 'form', 'label', 'list', 'readonly']
}
</script>
@@ -0,0 +1,25 @@
<template>
<div class="mb-2">
<label v-if="label">{{ label }}</label>
<v-switch
v-model="form[input_name]"
color="primary"
:value="1"
hide-details
></v-switch>
<div class="text-danger text-left" v-if="form.errors.has(input_name)" v-text="form.errors.get(input_name)"></div>
</div>
</template>
<script type="text/javascript">
import {ToggleButton} from 'vue-js-toggle-button'
export default {
props: {
input_name: String,
form: Object,
label: String
}
}
</script>
@@ -0,0 +1,31 @@
<template>
<div class="form-group" :class="{'has-error': form.errors.has(input_name)}">
<div>
<label v-if="label" for="name">{{ label }}</label>
</div>
<div class="col">
<div class="row">
<div :class="full == true ? 'col-12' : 'col-3'" class="p-0" v-if="list.data" v-for="(item, $index) in list.data" :key="$index">
<div class="float-left mr-3">{{ item.value }}</div>
<div class="float-right">
<v-switch
:name="input_name"
v-model="form[input_name]"
color="primary"
:value="item.key"
hide-details
@keydown="form.errors.clear(input_name)"
></v-switch>
</div>
</div>
</div>
</div>
<div class="text-danger text-left" v-if="form.errors.has(input_name)" v-text="form.errors.get(input_name)"></div>
</div>
</template>
<script type="text/javascript">
export default {
props: ['input_name', 'form', 'list', 'label', 'full']
}
</script>
@@ -0,0 +1,21 @@
<template>
<div class="mb-2">
<label v-if="label">{{ label }}</label>
<v-switch
v-model="form[input_name]"
color="primary"
:value="value"
hide-details
></v-switch>
<div class="text-danger text-left" v-if="form.errors.has(input_name)" v-text="form.errors.get(input_name)"></div>
</div>
</template>
<script type="text/javascript">
import {ToggleButton} from 'vue-js-toggle-button'
export default {
props: ['input_name', 'value', 'form', 'label', 'value']
}
</script>
@@ -0,0 +1,27 @@
<template>
<div class="form-group" :class="{'has-error': form.errors.has(input_name)}">
<label v-if="label" for="name">{{ label }}</label>
<textarea
:name="input_name"
class="form-control"
:placeholder="placeholder"
v-model="form[input_name]"
@keydown="form.errors.clear(input_name)"
:readonly="readonly"
>
</textarea>
<div class="text-danger text-left" v-if="form.errors.has(input_name)" v-text="form.errors.get(input_name)"></div>
</div>
</template>
<script type="text/javascript">
export default {
props: {
input_name: String,
form: Object,
placeholder: String,
label: String,
readonly: Boolean
}
}
</script>
@@ -0,0 +1,18 @@
<template>
<div class="form-group">
<label v-if="label" for="name">{{ label }}</label>
<textarea type="text"
rows="8" cols="50"
class="form-control"
:value="value"
:readonly="true"
>
</textarea>
</div>
</template>
<script type="text/javascript">
export default {
props: ['value', 'label']
}
</script>
@@ -0,0 +1,26 @@
<template>
<div class="form-group" :class="{'has-error': form.errors.has(input_name)}">
<label v-if="label">{{ label }}</label>
<input type="text"
:name="input_name"
class="form-control"
:placeholder="placeholder"
v-model="form[input_name]"
@keydown="form.errors.clear(input_name)"
:readonly="readonly"
>
<div class="text-danger text-left" v-if="form.errors.has(input_name)" v-text="form.errors.get(input_name)"></div>
</div>
</template>
<script type="text/javascript">
export default {
props: {
input_name: String,
form: Object,
placeholder: String,
label: String,
readonly: Boolean
}
}
</script>
@@ -0,0 +1,53 @@
<template>
<div class="form-group" :class="{'has-error': form.errors.has(input_name)}">
<div class="mb-2">
<label v-if="label" for="name">{{ label }}</label>
</div>
<b-row class="mb-2"
v-for="(input, $index) in form[input_name]" :key="$index"
>
<b-col md="10">
<input type="text"
class="form-control mb-1"
v-model="form[input_name][$index]"
@keydown="form.errors.clear(input_name)"
>
</b-col>
<b-col md="2" class="text-right">
<button type="button" class="btn btn-xs btn-danger" @click="removeField($index)">
<i class="fa fa-trash"></i>
</button>
</b-col>
</b-row>
<div>
<button
class="btn btn-xs btn-light"
@click="addField"
>
<i class="fa fa-plus"></i> Add Option
</button>
</div>
<div class="text-danger text-left" v-if="form.errors.has(input_name)" v-text="form.errors.get(input_name)"></div>
</div>
</template>
<script type="text/javascript">
export default {
props: ['input_name', 'form', 'placeholder', 'label', 'readonly'],
methods: {
addField() {
let vm = this;
vm.form[vm.input_name].push([]);
},
removeField(index) {
let vm = this;
vm.form[vm.input_name].splice(index, 1);
}
},
created() {
let vm = this;
}
}
</script>
@@ -0,0 +1,23 @@
<template>
<div class="form-group">
<label v-if="label" for="name">{{ label }}</label>
<b-row class="mb-2"
v-for="(input, $index) in form[input_name]" :key="$index"
>
<b-col md="12">
<input type="text"
class="form-control mb-1"
v-model="form[input_name][$index]"
:readonly="true"
>
</b-col>
</b-row>
</div>
</template>
<script type="text/javascript">
export default {
props: ['input_name', 'form', 'label'],
}
</script>
@@ -0,0 +1,19 @@
<template>
<div class="form-group">
<label v-if="label" for="name">{{ label }}</label>
<input type="text"
class="form-control"
:value="value"
:readonly="true"
>
</div>
</template>
<script type="text/javascript">
export default {
props: {
label: String,
value: String
}
}
</script>
@@ -0,0 +1,56 @@
<template>
<div>
<b-modal :id="'cropper-modal'" size="lg" scrollable>
<template v-slot:modal-title>
<h4 class="modal-title">Crop Image</h4>
</template>
<vue-cropper
ref='cropper'
:guides="true"
:view-mode="2"
drag-mode="crop"
:auto-crop-area="0.5"
:min-container-width="250"
:min-container-height="180"
:background="true"
:rotatable="true"
:src="img"
alt="Source Image"
:img-style="{ 'width': '400px', 'height': '300px' }"
>
</vue-cropper>
<template v-slot:modal-footer>
<form>
<v-btn
:block="true"
type="button"
color="primary"
class="white--text"
@click="cropImage"
>
Crop
</v-btn>
</form>
</template>
</b-modal>
</div>
</template>
<script>
import VueCropper from 'vue-cropperjs';
export default {
props: ['img'],
components: {
VueCropper
},
methods: {
cropImage() {
let vm = this;
let cropImg = '';
cropImg = this.$refs.cropper.getCroppedCanvas().toDataURL();
vm.$bvModal.hide('cropper-modal');
vm.$emit('clicked', cropImg);
}
}
}
</script>
@@ -0,0 +1,48 @@
<template>
<div>
<b-modal :id="'img-pop-modal'" size="lg" scrollable>
<template v-slot:modal-title>
<h4 class="modal-title">{{ label }}</h4>
</template>
<div v-if="img[0]" class="text-center">
<img class="img-fluid"
:src="img[0].file_info.original.file"
width="300px" height="300px"
>
</div>
<template v-slot:modal-footer>
<form>
<v-btn
depressed
small
type="button"
color="warning"
class="m-0"
@click="$bvModal.hide('img-pop-modal')"
>
Close
</v-btn>
</form>
</template>
</b-modal>
</div>
</template>
<script>
import VueCropper from 'vue-cropperjs';
export default {
props: ['label', 'img'],
components: {
VueCropper
},
methods: {
cropImage() {
let vm = this;
let cropImg = '';
cropImg = this.$refs.cropper.getCroppedCanvas().toDataURL();
vm.$bvModal.hide('cropper-modal');
vm.$emit('clicked', cropImg);
}
}
}
</script>
@@ -0,0 +1,31 @@
<template>
<tr>
<td class="pl-0 pr-0" :colspan="colspan">
<ul class="pagination pull-right">
<li v-if="pervious_page" class="page-item">
<span
@click="$emit('clicked', pervious_page)"
class="page-link"
title="left" value="">
«
</span>
</li>
<li v-if="next_page" class="page-item">
<span
@click="$emit('clicked', next_page)"
class="page-link"
title="right" value="">
»
</span>
</li>
</ul>
</td>
</tr>
</template>
<script type="text/javascript">
export default {
props: ['pervious_page', 'next_page', 'colspan']
}
</script>
@@ -0,0 +1,21 @@
<template>
<div class="app-page-title">
<div class="page-title-wrapper">
<div class="page-title-heading">
<div class="page-title-icon">
<i :class="icon"></i>
</div>
<div>
{{heading}}
</div>
</div>
</div>
</div>
</template>
<script>
export default {
props: ['icon', 'heading'],
}
</script>
+18
View File
@@ -0,0 +1,18 @@
window.Vue = require('vue');
require('./bootstrap');
import router from './router'
import App from './App'
import DefaultLayout from './layouts/DefaultLayout.vue';
import PageLayout from './layouts/PageLayout.vue';
Vue.component('default-layout', DefaultLayout);
Vue.component('userpages-layout', PageLayout);
const app = new Vue({
el: '#app',
router,
template: '<App/>',
components: { App }
});
+53
View File
@@ -0,0 +1,53 @@
const En = {
// Input
'name': 'name',
'username': 'username',
'email': 'email',
'password': 'password',
'role_id': 'role',
'permission_id': 'permission',
'phone_code': 'phone code',
'code': 'code',
'currency_id': 'currency',
'language_id': 'language',
'symbol': 'symbol',
'sell': 'sell',
'buy': 'buy',
'description': 'description',
'min_file_size': 'min file size',
'max_file_size': 'max file size',
'credit_amount': 'credit amount',
'media_property_type_id': 'media property type',
'type': 'type',
'mime_type_id': 'mime type',
'trending_count': 'trending count',
'bank_name': 'bank name',
'holder_name': 'holder name',
'holder_no': 'holder no',
'country_id': 'country',
// Error
'incorrect_account_or_password': 'Incorrect account or password',
'this_field_required': 'This %(att)s field required',
'incorrect_email_format': 'Incorrect email format',
'the_content_has_already_been_taken': 'The %(att)s has already been taken',
'the_content_confirmation_does_not_match': 'The %(att)s confirmation does not match',
'incorrect_image_format': 'Incorrect image format',
'incorrect_excel_format': 'Incorrect excel format',
'the_content_must_be_a_number': 'The %(att)s must be a number',
'the_content_must_be_greater_than_0': 'The %(att)s must be greater than 0',
'the_content_not_exist': 'The %(att)s not exist',
'the_content_must_be_at_least_10_characters': 'The %(att)s must be at least 10 characters',
'the_content_must_be_less_than_100': 'The %(att)s must be less than 100',
'status_error': 'status not allow update',
// toasted
'have_been_successfully_created': '%(value[0].att)s ( %(value[1].att)s ) have been successfully created',
'have_been_successfully_updated': '%(value[0].att)s ( %(value[1].att)s ) have been successfully updated',
'have_been_successfully_imported': '%(value[0].att)s have been successfully imported',
'error_mass_import': '%(value[0].att)s excel file have error when import',
};
export default En;
@@ -0,0 +1,71 @@
<template>
<v-app id="benesApp">
<div v-if="!loading" class="app-container app-theme-white">
<transition name="fade" mode="out-in" appear>
<app-header
:user_detail="user_detail"
>
</app-header>
</transition>
<transition name="fade" mode="out-in" appear>
<app-sidebar
:permission_list="permission_list"
>
</app-sidebar>
</transition>
<div class="app-main__outer">
<div class="app-main__inner">
<slot></slot>
</div>
<transition name="fade" mode="out-in" appear>
<app-footer></app-footer>
</transition>
</div>
</div>
<div v-if="loading" class="main-loading-box">
<vue-element-loading :active="true" spinner="line-scale" color="var(--primary)"></vue-element-loading>
</div>
</v-app>
</template>
<script>
import AppHeader from "./parts/Header";
import AppSidebar from "./parts/Sidebar";
import AppFooter from "./parts/Footer";
export default {
name: 'app',
components: {
AppHeader,
AppSidebar,
AppFooter
},
data() {
return {
form: new Form({}),
isConfigOpen: false,
user_detail: {},
permission_list: [],
loading: true,
}
},
methods: {
getUserDetail() {
let vm = this;
let url = 'user/self';
General.getCall(url, vm.form).then(response => {
if (response.data) {
vm.user_detail = response.data;
vm.permission_list = response.data.permission_list;
vm.loading = false;
}
});
}
},
created() {
let vm = this;
vm.getUserDetail();
},
}
</script>
+14
View File
@@ -0,0 +1,14 @@
<template>
<v-app id="benesApp">
<div class="app-container app-theme-white">
<slot></slot>
</div>
</v-app>
</template>
<script>
export default {
name: 'app',
}
</script>
@@ -0,0 +1,22 @@
<template>
<div v-if="menu_item_list" class="mb-3">
<nav aria-label="breadcrumb">
<ol class="breadcrumb">
<li class="breadcrumb-item" :class="menu.active ? 'active' : ''" v-for="(menu, $index) in menu_item_list" :key="$index">
<router-link :to="menu.path" tag="button">
{{ menu.title }}
</router-link>
</li>
</ol>
</nav>
</div>
</template>
<script>
export default {
props: ['menu_item_list'],
}
</script>
@@ -0,0 +1,21 @@
<template>
<div class="app-footer">
<div class="app-footer__inner">
<div class="app-footer-left">
Copyright &copy; {{ site_title }} {{ year }}
</div>
</div>
</div>
</template>
<script>
export default {
data() {
let vm = this;
return {
site_title: Constants.site.title,
year: General.getYear()
}
}
}
</script>
@@ -0,0 +1,86 @@
<template>
<div :class="headerbg" class="app-header header-shadow bg-plum-plate text-lighter">
<div class="app-header__content">
<div class="app-header-right">
<HeaderUserArea
:user_detail="user_detail"
/>
</div>
</div>
<div class="app-header__mobile-menu">
<div>
<button type="button" class="hamburger close-sidebar-btn hamburger--elastic" :class="{ 'is-active' : isOpen }" @click="toggleMobile('closed-sidebar-open')">
<span class="hamburger-box">
<span class="hamburger-inner"></span>
</span>
</button>
</div>
</div>
<div class="app-header__menu">
<span>
<b-button class="btn-icon btn-icon-only" variant="primary" size="sm" :class="{ 'active' : isOpenMobileMenu }" @click="toggleMobile2('header-menu-open')">
<div class="btn-icon-wrapper">
<i class="fa fa-ellipsis-v"></i>
</div>
</b-button>
</span>
</div>
</div>
</template>
<script>
import HeaderUserArea from './HeaderUserArea';
export default {
name: "Header",
components: {
HeaderUserArea,
},
data() {
return {
isOpen: false,
isOpenMobileMenu: false,
}
},
props: {
headerbg: String,
user_detail: Object
},
methods: {
toggleMobile(className) {
let vm = this;
const bu = document.getElementsByClassName('close-sidebar-btn')[0];
const el = document.body;
if (bu.classList.contains('is-active')) {
vm.isOpen = false;
bu.classList.remove('is-active');
}
else {
vm.isOpen = true;
bu.classList.add('is-active');
}
if (vm.isOpen) {
el.classList.add(className);
} else {
el.classList.remove(className);
}
},
toggleMobile2(className) {
let vm = this;
const el = document.body;
vm.isOpenMobileMenu = !vm.isOpenMobileMenu;
if (vm.isOpenMobileMenu) {
el.classList.add(className);
} else {
el.classList.remove(className);
}
},
}
};
</script>
@@ -0,0 +1,87 @@
<template>
<div v-if="user_detail.username" class="d-flex">
<div class="header-btn-lg pr-0">
<div class="widget-content p-0">
<div class="widget-content-wrapper">
<div class="widget-content-left">
<b-dropdown toggle-class="p-0 mr-2" menu-class="dropdown-menu-lg" variant="link" no-caret right>
<span slot="button-content">
<div class="icon-wrapper icon-wrapper-alt rounded-circle">
<img width="50" height="50" class="rounded-circle" :src="user_detail.img[0].file_info.small.file" alt="">
</div>
</span>
<div class="dropdown-menu-header">
<div class="dropdown-menu-header-inner bg-info">
<div class="menu-header-image opacity-2 dd-header-bg-6"></div>
<div class="menu-header-content text-left">
<div class="widget-content p-0">
<div class="widget-content-wrapper">
<div class="widget-content-left mr-3">
<img width="42" class="rounded-circle" :src="user_detail.img[0].file_info.small.file" alt="">
</div>
<div class="widget-content-left">
<div class="widget-heading">{{ user_detail.username}}</div>
<div class="widget-subheading opacity-8">{{ user_detail.role_list[0].username }}</div>
</div>
<div class="widget-content-right mr-2">
<button
@click="logout"
class="btn-shadow btn-shine btn btn-focus"
>
Logout
</button>
</div>
</div>
</div>
</div>
</div>
</div>
<div class="scroll-area-xs" style="height: 80px;">
<vue-perfect-scrollbar class="scrollbar-container" v-once>
<ul class="nav flex-column">
<li class="nav-item-header nav-item">My Account</li>
<li class="nav-item">
<router-link
:to="'/admin/profile/update'"
class="nav-link"
title="Profile"
>
My Profile
</router-link>
</li>
</ul>
</vue-perfect-scrollbar>
</div>
</b-dropdown>
</div>
<div class="widget-content-left ml-3 header-user-info">
<div class="widget-heading">{{ user_detail.username }}</div>
<div class="widget-subheading">{{ user_detail.role_list[0].name }}</div>
</div>
</div>
</div>
</div>
</div>
</template>
<script>
import VuePerfectScrollbar from 'vue-perfect-scrollbar'
import VueCircle from 'vue2-circle-progress'
export default {
components: {
VuePerfectScrollbar,
VueCircle,
},
props: {
user_detail: Object
},
methods: {
logout() {
General.logout();
}
}
}
</script>
+120
View File
@@ -0,0 +1,120 @@
<template>
<div class="app-sidebar sidebar-shadow bg-plum-plate text-lighter" @mouseover="toggleSidebarHover('add','closed-sidebar-open')" @mouseleave="toggleSidebarHover('remove','closed-sidebar-open')">
<div class="app-header__logo">
<div class="text-white m-2">
<router-link
:to="'/'"
class="btn-link"
>
<div class="logo-src" :style='{ background: "url(" + share_url + "logo-white.png" + ")" }'></div>
</router-link>
</div>
<div class="header__pane ml-auto">
<button type="button" class="hamburger close-sidebar-btn hamburger--elastic" v-bind:class="{ 'is-active' : is_open }" @click="toggleBodyClass('closed-sidebar')">
<span class="hamburger-box">
<span class="hamburger-inner"></span>
</span>
</button>
</div>
</div>
<div class="app-sidebar-content">
<vue-perfect-scrollbar v-if="menu[0]" class="app-sidebar-scroll" v-once>
<sidebar-menu
showOneChild
:menu="menu"
@item-click="onItemClick"
/>
</vue-perfect-scrollbar>
</div>
</div>
</template>
<script>
import { SidebarMenu } from 'vue-sidebar-menu'
import VuePerfectScrollbar from 'vue-perfect-scrollbar'
export default {
props: {
permission_list: Array
},
components: {
SidebarMenu,
VuePerfectScrollbar
},
data() {
return {
is_open: false,
sidebar_active: false,
menu: [],
collapsed: true,
window_width: 0,
share_url: ENV.SHARE_URL,
site_short_title: Constants.site.short_title,
}
},
methods: {
onItemClick(event, item) {
let vm = this;
if (!item.child) {
const el = document.body;
el.classList.remove('closed-sidebar-open');
const bu = document.getElementsByClassName('close-sidebar-btn')[0];
bu.classList.remove('is-active');
}
},
toggleBodyClass(className) {
let vm = this;
const el = document.body;
vm.is_open = !vm.is_open;
if (vm.is_open) {
el.classList.add(className);
} else {
el.classList.remove(className);
}
},
toggleSidebarHover(add, className) {
let vm = this;
const el = document.body;
vm.sidebar_active = !vm.sidebar_active;
vm.window_width = document.documentElement.clientWidth;
if (vm.window_width > '992') {
if (add === 'add') {
el.classList.add(className);
} else {
el.classList.remove(className);
}
}
},
getWindowWidth() {
let vm = this;
const el = document.body;
vm.window_width = document.documentElement.clientWidth;
el.classList.add('closed-sidebar', 'closed-sidebar-md');
},
loadMenu() {
let vm = this;
vm.menu = NavigationAccess.menuDefine(vm.permission_list);
}
},
created() {
let vm = this;
vm.loadMenu();
},
mounted() {
let vm = this;
vm.$nextTick(function () {
window.addEventListener('resize', vm.getWindowWidth);
//Init
vm.getWindowWidth()
})
},
beforeDestroy() {
let vm = this;
window.removeEventListener('resize', vm.getWindowWidth);
}
}
</script>
@@ -0,0 +1,34 @@
<template>
<div>
<page-title :heading=heading :icon=icon></page-title>
</div>
</template>
<script>
import PageTitle from "../../components/titles/PageTitle.vue";
export default {
metaInfo() {
let vm = this;
return {
title: Constants.site.title,
titleTemplate: '%s | ' + vm.$router.currentRoute.name
}
},
components: {
PageTitle,
},
data() {
return {
heading: 'Analytics Dashboard',
icon: 'fa fa-home',
}
},
methods: {
},
created() {
let vm = this;
}
}
</script>
+132
View File
@@ -0,0 +1,132 @@
<template>
<div>
<div class="vh-100 bg-plum-plate bg-animation">
<div class="">
<div class="app-header header-shadow bg-light text-lighter">
<router-link
:to="'/'"
class="btn-link pl-3"
>
<div class="logo-src" :style='{ background: "url(" + share_url + "favicon.png" + ")" }'></div>
</router-link>
<div class="pl-3 app-header-title">
<h5 class="card-title">Login</h5>
</div>
</div>
<div class="d-flex justify-content-center align-items-center">
<b-col md="4" class="top-margin-100">
<div class="mx-auto p-0 m-0">
<form @submit.prevent="onSubmit" @keydown="form.errors.clear($event.target.name)">
<div class="modal-content">
<div class="modal-body">
<div class="h5 modal-title text-center">
<div class="m-3 text-center">
<img class="img-fluid big-logo" :src="share_url + 'logo.png'">
</div>
</div>
<!-- ///////////////////////////////////////////////////// -->
<text-input
:input_name="'email'"
:form="form"
:label="'Email'"
>
</text-input>
<!-- ///////////////////////////////////////////////////// -->
<password-input
:input_name="'password'"
:form="form"
:label="'Password'"
>
</password-input>
<!-- ///////////////////////////////////////////////////// -->
</div>
<div class="modal-footer">
<div>
<v-btn
:loading="loading"
:disabled="loading"
:block="true"
type="submit"
color="primary"
class="white--text"
@click="loader = 'loading'"
>
Login
</v-btn>
</div>
</div>
</div>
</form>
</div>
</b-col>
</div>
<div class="footer text-center text-white opacity-8 p-3">
Copyright &copy; {{ site_title }} {{ year }}
</div>
</div>
</div>
</div>
</template>
<script>
import TextInput from '../../components/inputs/TextInput';
import PasswordInput from '../../components/inputs/PasswordInput';
export default {
metaInfo() {
let vm = this;
return {
title: Constants.site.title,
titleTemplate: '%s | ' + vm.$router.currentRoute.name
}
},
components: {
TextInput,
PasswordInput
},
data() {
let vm = this;
return {
form: new Form({
email: '',
password: ''
}),
site_title: Constants.site.title,
site_short_title: Constants.site.short_title,
share_url: ENV.SHARE_URL,
year: General.getYear(),
loader: null,
loading: false
}
},
methods: {
onSubmit() {
let vm = this;
const l = vm.loader
vm[l] = !vm[l]
vm.form.post(
ENV.API_URL + 'login',
{
'App-Key': ENV.APP_KEY
}
)
.then(response => {
localStorage.setItem('user_detail',JSON.stringify(response.data))
localStorage.setItem('user_jwt', response.access_token);
if (localStorage.getItem('user_jwt') != null) {
window.location.replace(ENV.APP_URL);
}
})
.catch(function (error) {
vm[l] = false
vm.loader = null;
});
}
},
created() {
let vm = this;
}
}
</script>
@@ -0,0 +1,53 @@
<template>
<div>
<div class="vh-100 bg-plum-plate bg-animation">
<div class="">
<div class="app-header header-shadow bg-light text-lighter">
<router-link
:to="'/'"
class="btn-link pl-3"
>
<div class="logo-src" :style='{ background: "url(" + share_url + "favicon.png" + ")" }'></div>
</router-link>
<div class="pl-3 app-header-title">
<h5 class="card-title">Page 404</h5>
</div>
</div>
<div class="d-flex justify-content-center align-items-center">
<b-col class="mobile-none top-margin-100" md="5">
<div class="mb-3 text-center">
<img class="img-fluid big-logo" :src="share_url + 'logo.png'">
</div>
<div class="text-white text-center">
<h1 class="mb-2">Page 404</h1>
</div>
</b-col>
</div>
<div class="footer text-center text-white opacity-8 p-3">
Copyright &copy; {{ site_title }} {{ year }}
</div>
</div>
</div>
</div>
</template>
<script>
export default {
metaInfo() {
let vm = this;
return {
title: Constants.site.title,
titleTemplate: '%s | ' + vm.$router.currentRoute.name
}
},
data() {
let vm = this;
return {
share_url: ENV.SHARE_URL,
site_title: Constants.site.title,
year: General.getYear()
}
}
}
</script>
+59
View File
@@ -0,0 +1,59 @@
<template>
<div>
<page-title
:heading="heading"
:icon="icon"
:add="true"
></page-title>
<breadcrumb
:menu_item_list="menu_item_list"
></breadcrumb>
<user-form
:mode="'create'"
></user-form>
</div>
</template>
<script>
import PageTitle from "../../components/titles/PageTitle";
import Breadcrumb from "../../layouts/parts/Breadcrumb";
import UserForm from "./Form";
export default {
metaInfo() {
let vm = this;
return {
title: Constants.site.title,
titleTemplate: '%s | ' + vm.$router.currentRoute.name
}
},
components: {
PageTitle,
Breadcrumb,
UserForm
},
data() {
let vm = this;
return {
heading: 'User Detail',
icon: 'fa fa-plus',
menu_item_list: [
{
title: 'Dashboard',
active: false,
path: '/'
},
{
title: 'User List',
active: false,
path: '/user/list'
},
{
title: 'User Detail',
active: true,
path: '/user/detail/' + vm.$route.params.hash_id
},
],
}
}
}
</script>
+81
View File
@@ -0,0 +1,81 @@
<template>
<b-col md="12">
<form @submit.prevent="$emit('clicked')" @keydown="form.errors.clear($event.target.name)">
<text-input
:input_name="'name'"
:form="form"
:label="'Name'"
>
</text-input>
<text-input
:input_name="'email'"
:form="form"
:label="'Email'"
>
</text-input>
<select-option-auto-complete-input
:input_name="'status'"
:form="form"
:exist_data="exist_user_status"
:placeholder="''"
:label="'Status'"
:data_set="user_status"
:array="true"
></select-option-auto-complete-input>
<v-btn
depressed
small
type="submit"
color="primary"
class="m-0"
>
Submit
</v-btn>
<v-btn
depressed
small
type="button"
color="warning"
class="m-0"
@click="$emit('reset')"
>
Reset
</v-btn>
</form>
</b-col>
</template>
<script>
import TextInput from '../../components/inputs/TextInput';
import SelectOptionAutoCompleteInput from '../../components/inputs/SelectOptionAutoCompleteInput';
export default {
props: {
form: Object
},
components: {
TextInput,
SelectOptionAutoCompleteInput
},
data() {
let vm = this;
return {
exist_user_status: [],
user_status: General.autoCompleteKeyConvert(Constants.user_status)
}
},
methods: {
initForm() {
let vm = this;
if (vm.form.status[0]) {
let status_list = [];
vm.exist_user_status = General.initFormArray(vm.user_status, vm.form.status);
}
}
},
created() {
let vm = this;
vm.initForm();
}
}
</script>
+97
View File
@@ -0,0 +1,97 @@
<template>
<b-card class="main-card mb-3">
<div v-if="!this_user.data" class="text-center">
<vue-element-loading :active="true" spinner="line-scale" color="var(--primary)"></vue-element-loading>
</div>
<div v-if="this_user.data">
<form-wizard
@on-complete="onComplete"
title=""
subtitle=""
finish-button-text="Back"
@keydown="form.errors.clear($event.target.name)"
color="var(--primary)"
>
<tab-content
title="Details"
icon="fa fa-info-circle"
>
<image-read-only-input
:label="'Profile'"
:exist_img="this_user.data.img"
>
</image-read-only-input>
<!-- ///////////////////////////////////////////////////// -->
<text-read-only-input
:label="'Username'"
:value="this_user.data.username"
>
</text-read-only-input>
<!-- ///////////////////////////////////////////////////// -->
<text-read-only-input
:label="'Email'"
:value="this_user.data.email"
>
</text-read-only-input>
<!-- ///////////////////////////////////////////////////// -->
<text-read-only-input
:label="'Country'"
:value="this_user.data.country_list[0].name"
>
</text-read-only-input>
<!-- ///////////////////////////////////////////////////// -->
<text-read-only-input
:label="'Status'"
:value="this_user.data.status[0].name"
>
</text-read-only-input>
<!-- ///////////////////////////////////////////////////// -->
</tab-content>
</form-wizard>
</div>
</b-card>
</template>
<script>
import TextReadOnlyInput from '../../components/inputs/TextReadOnlyInput';
import ImageReadOnlyInput from '../../components/inputs/ImageReadOnlyInput';
export default {
props: {
mode: String
},
components: {
TextReadOnlyInput,
ImageReadOnlyInput
},
data() {
return {
this_user: [],
}
},
methods: {
onComplete() {
let vm = this;
vm.$router.push('/user/list');
return true;
},
loadThisUser() {
let vm = this;
let url = 'user/show/' + vm.$route.params.hash_id;
let form = new Form({});
General.getCall(url, form).then(response => {
vm.this_user = response;
vm.convertStatus();
});
},
convertStatus() {
let vm = this;
vm.this_user.data.status = General.initFormArray(Constants.admin_status, [vm.this_user.data.status]);
},
},
created() {
let vm = this;
vm.loadThisUser();
}
}
</script>
+51
View File
@@ -0,0 +1,51 @@
<template>
<div>
<page-title
:heading="heading"
:icon="icon"
:add="true"
></page-title>
<breadcrumb
:menu_item_list=menu_item_list
></breadcrumb>
<user-table></user-table>
</div>
</template>
<script>
import PageTitle from "../../components/titles/PageTitle";
import Breadcrumb from "../../layouts/parts/Breadcrumb";
import UserTable from "./Table";
export default {
metaInfo() {
let vm = this;
return {
title: Constants.site.title,
titleTemplate: '%s | ' + vm.$router.currentRoute.name
}
},
components: {
PageTitle,
Breadcrumb,
UserTable
},
data() {
return {
heading: 'User List',
icon: 'fa fa-list',
menu_item_list: [
{
title: 'Dashboard',
active: false,
path: '/'
},
{
title: 'User List',
active: true,
path: 'list'
},
],
}
}
}
</script>
+199
View File
@@ -0,0 +1,199 @@
<template>
<div>
<b-card class="main-card mb-4">
<b-row class="mb-2" no-gutters>
<b-col md="6">
<button type="button" class="btn btn-primary" @click="filterToggle">
<i class="fa fa-filter"></i>
</button>
<div v-if="filter_status" class="filter dropdown-menu show">
<user-filter
:form="form"
@clicked="filterSubmit"
@reset="FilterReset"
></user-filter>
</div>
</b-col>
</b-row>
<table class="table table-striped">
<thead>
<paginate-nav
v-if="user_list.page"
:colspan="colspan"
:pervious_page="user_list.page.pervious_page"
:next_page="user_list.page.next_page"
@clicked="dataHandler"
></paginate-nav>
<tr>
<th>#</th>
<th>Profile</th>
<th style="width: 25%">Details</th>
<th>Status</th>
<th>Created</th>
<th>Actions</th>
</tr>
</thead>
<tbody>
<tr v-if="user_list.data" v-for="(user, $index) in user_list.data" :key="$index">
<td>{{ $index + 1 }}</td>
<td>
<img class="img-fluid pointer-cursor"
:src="user.img[0].file_info.original.file" width="60px" height="60px"
@click="ImgPopModalToggle(user.img)"
>
</td>
<td>
<div><b>Name</b>: {{ user.username }}</div>
<div><b>Email</b>: {{ user.email }}</div>
<div><b>Country</b>: {{ user.country_list[0].name }}</div>
</td>
<td>
<span :class="'badge badge-' + user.status[0].color">{{ user.status[0].name }}</span>
</td>
<td>
{{ user.created_at }}
</td>
<td>
<router-link
v-if="permission_list.includes('view user')"
:to="'detail/' + user.hash_id"
class="btn btn-xs btn-light"
title="View"
>
<i class="fa fa-eye"></i>
</router-link>
</td>
</tr>
<tr v-if="!user_list.data || !user_list.data[0]">
<td class="text-center" :colspan="colspan">
<vue-element-loading v-if="loading" :active="true" spinner="line-scale" color="var(--primary)"></vue-element-loading>
<span v-if="!loading">Empty</span>
</td>
</tr>
</tbody>
<tfoot v-if="user_list.page">
<paginate-nav
:colspan="colspan"
:pervious_page="user_list.page.pervious_page"
:next_page="user_list.page.next_page"
@clicked="dataHandler"
>
</paginate-nav>
<tr>
<td class="text-right" :colspan="colspan">
<small>Total: {{ user_list.total }}</small>
</td>
</tr>
</tfoot>
</table>
</b-card>
<img-pop-modal
:label="'Profile'"
:img="selected_img"
>
</img-pop-modal>
</div>
</template>
<script>
import userFilter from "./Filter";
import ImgPopModal from "../../components/modals/ImgPopModal";
import PaginateNav from '../../components/paginations/NextPerviousPagination';
export default {
components: {
userFilter,
ImgPopModal,
PaginateNav
},
data() {
return {
form_permission: new Form({}),
form: new Form({
username: General.getURLParam('username'),
email: General.getURLParam('email'),
status: General.returnAray(General.getURLParam('status[]')),
page: General.getURLParam('page')
}),
colspan: 6,
filter_status: false,
loading: true,
user_list: [],
selected_img: [],
permission_list: []
}
},
methods: {
ImgPopModalToggle(img) {
let vm = this;
vm.selected_img = img;
vm.$bvModal.show('img-pop-modal');
},
getPermissionList() {
let vm = this;
let url = 'admin/self';
let params = {}
General.getCall(url, vm.form_permission).then(response => {
vm.permission_list = response.data.permission_list;
});
},
getUserList() {
let vm = this;
let url = 'user/list';
vm.tableCall(url);
},
tableCall(url) {
let vm = this;
vm.loading = true;
General.tableCall(url, vm.form).then(response => {
vm.loading = false;
vm.user_list = response;
vm.form.page = General.getURLParam('page');
vm.convertStatus();
});
},
convertStatus() {
let vm = this;
vm.user_list.data.forEach(function(element, index) {
element.status = General.initFormArray(Constants.user_status, [element.status]);
});
},
dataHandler(url) {
let vm = this;
vm.form.page = General.getStringParam('page', url);
vm.tableCall('user/list');
},
filterToggle() {
let vm = this;
vm.filter_status = !vm.filter_status;
},
filterCloseClick(e) {
let vm = this;
if (!vm.$el.contains(e.target) && !e.target.classList.contains('filter')) {
vm.filter_status = false;
}
},
filterSubmit() {
let vm = this;
let url = 'user/list';
vm.tableCall(url);
vm.form.page = '';
vm.filterToggle();
},
FilterReset() {
let vm = this;
vm.form.reset();
vm.filterSubmit();
}
},
mounted() {
let vm = this;
document.addEventListener('click', vm.filterCloseClick);
},
created() {
let vm = this;
vm.getPermissionList();
vm.getUserList();
}
}
</script>
+168
View File
@@ -0,0 +1,168 @@
import Vue from 'vue'
import Router from 'vue-router'
Vue.use(Router);
import Logins from './pages/logins/Index';
// import Dashboards from './pages/dashboards/Index';
// import UserList from './pages/users/List';
// import UserDetail from './pages/users/Detail';
// import AdminList from './pages/admins/List';
// import AdminCreate from './pages/admins/Create';
// import AdminEdit from './pages/admins/Edit';
// import AdminProfile from './pages/admins/Profile';
// import AdminRoleList from './pages/admin-roles/List';
// import AdminRoleCreate from './pages/admin-roles/Create';
// import AdminRoleEdit from './pages/admin-roles/Edit';
// import PageNotFound from './pages/page-not-founds/Index';
let router = new Router({
mode: 'history',
base: process.env.MIX_SUB_URL,
scrollBehavior() {
return window.scrollTo({ top: 0, behavior: 'smooth' });
},
routes: [
/////////////////////////////////////////////////////
// Login
{
path: '/login',
name: 'Login',
meta: {
layout: 'userpages',
guest: true
},
component: Logins
},
/////////////////////////////////////////////////////
// // Dashboard
// {
// path: '/',
// name: 'Dashboards',
// meta: {
// auth: true
// },
// component: Dashboards
// },
// /////////////////////////////////////////////////////
// // User
// {
// path: '/user/list',
// name: 'User List',
// meta: {
// auth: true
// },
// component: UserList
// },
// {
// path: '/user/detail/:hash_id',
// name: 'User Detail',
// meta: {
// auth: true
// },
// component: UserDetail
// },
// /////////////////////////////////////////////////////
// // Admin
// {
// path: '/admin/list',
// name: 'Admin List',
// meta: {
// auth: true
// },
// component: AdminList
// },
// {
// path: '/admin/create',
// name: 'Admin Create',
// meta: {
// auth: true
// },
// component: AdminCreate
// },
// {
// path: '/admin/edit/:hash_id',
// name: 'Admin Edit',
// meta: {
// auth: true
// },
// component: AdminEdit
// },
// {
// path: '/admin/profile/update',
// name: 'Admin Profile',
// meta: {
// auth: true
// },
// component: AdminProfile
// },
// /////////////////////////////////////////////////////
// // Admin Role
// {
// path: '/admin-role/list',
// name: 'Admin Role List',
// meta: {
// auth: true
// },
// component: AdminRoleList
// },
// {
// path: '/admin-role/create',
// name: 'Admin Role Create',
// meta: {
// auth: true
// },
// component: AdminRoleCreate
// },
// {
// path: '/admin-role/edit/:hash_id',
// name: 'Admin Role Edit',
// meta: {
// auth: true
// },
// component: AdminRoleEdit
// },
// /////////////////////////////////////////////////////
// // 404
// {
// path: '*',
// name: 'Page 404',
// meta: {
// layout: 'userpages',
// guest: true
// },
// component: PageNotFound
// },
// /////////////////////////////////////////////////////
]
});
// window.USER_DETAIL = localStorage.getItem('user_detail') != null ? JSON.parse(localStorage.getItem('user_detail')) : null;
window.TOKEN = localStorage.getItem('user_jwt');
router.beforeEach((to, from, next) => {
if(to.matched.some(record => record.meta.auth)) {
if (localStorage.getItem('user_jwt') == null) {
next({ path: 'login' })
}
else {
next();
}
}
if(to.matched.some(record => record.meta.without_auth)) {
if (localStorage.getItem('user_jwt') != null) {
next({ path: '/' })
}
else {
next();
}
}
next();
});
export default router;
+44
View File
@@ -0,0 +1,44 @@
const Constant = {
'site': {
'title': 'Unity',
'short_title': 'Unity',
'description': 'Unity'
},
'user_status': [
{
'id': 1,
'name': 'Active',
'color': 'primary'
},
{
'id': 2,
'name': 'De-active',
'color': 'danger'
},
],
'admin_status': [
{
'id': 1,
'name': 'Active',
'color': 'primary'
},
{
'id': 2,
'name': 'De-active',
'color': 'danger'
},
],
'role_status': [
{
'id': 1,
'name': 'Active',
'color': 'primary'
},
{
'id': 2,
'name': 'De-active',
'color': 'danger'
},
],
};
export default Constant;
+7
View File
@@ -0,0 +1,7 @@
const ENV = {
'APP_URL': process.env.MIX_APP_URL,
'API_URL': process.env.MIX_API_URL + 'user-backsys/api/',
'SHARE_URL': process.env.MIX_API_URL + 'share/',
'APP_KEY': process.env.MIX_APP_KEY
};
export default ENV;
+35
View File
@@ -0,0 +1,35 @@
class Errors {
constructor() {
this.errors = {};
}
has(field) {
return this.errors.hasOwnProperty(field);
}
any() {
return Object.keys(this.errors).length > 0;
}
get(field) {
if (this.errors[field]) {
return this.errors[field][0];
}
}
record(errors) {
this.errors = errors;
}
clear(field) {
if (field) {
delete this.errors[field];
return;
}
this.errors = {};
}
}
export default Errors;
+164
View File
@@ -0,0 +1,164 @@
import Errors from './errors';
class Form {
constructor(data) {
this.originalData = data;
for (let field in data) {
this[field] = data[field];
}
this.errors = new Errors();
}
data() {
let data = {};
for (let property in this.originalData) {
data[property] = this[property];
}
return data;
}
reset() {
for (let field in this.originalData) {
this[field] = '';
}
this.errors.clear();
}
get(url, header) {
return this.submit('get', url, header);
}
post(url, header) {
return this.submit('post', url, header);
}
put(url, header) {
return this.submit('put', url, header);
}
patch(url, header) {
return this.submit('patch', url, header);
}
delete(url, header) {
return this.submit('delete', url, header);
}
submit(method, url, header) {
let vm = this;
let params = vm.data();
let set = {};
if (method == 'get') {
set = {
method: method,
url: url,
params: params,
headers: header
}
}
else {
set = {
method: method,
url: url,
data: params,
headers: header
}
}
return new Promise((resolve, reject) => {
axios(set)
.then(response => {
vm.onSuccess(response.data);
resolve(response.data);
})
.catch(error => {
if (error.response.data.errors) {
let error_return = error.response.data.errors;
Object.keys(error_return).forEach(function (key) {
error.response.data.errors[key] = vm.errorCodeConvert(error_return[key], key);
});
vm.onFail(error.response.data.errors)
return reject(error.response.data.errors)
}
return reject(error)
});
});
}
onSuccess(data) {
// this.reset();
}
onFail(errors) {
this.errors.record(errors);
}
errorCodeConvert(code = [], key) {
let value = {
att: General.lang(key)
}
let return_message = [];
for (var i = 0; i < code.length; i++) {
if(code[i] == 'LOGIN_ERROR') {
return_message.push(General.lang('incorrect_account_or_password'));
}
else if (code[i] == 'REQUIRED') {
return_message.push(Sprintf(General.lang('this_field_required'), value));
}
else if(code[i] == 'EMAIL_ERROR') {
return_message.push(General.lang('incorrect_email_format'));
}
else if(code[i] == 'UNIQUE_ERRO') {
return_message.push(Sprintf(General.lang('the_content_has_already_been_taken'), value));
}
else if(code[i] == 'CONFIRM_ERROR') {
return_message.push(Sprintf(General.lang('the_content_confirmation_does_not_match'), value));
}
else if(code[i] == 'IMG_ERROR') {
return_message.push(Sprintf(General.lang('incorrect_image_format'), value));
}
else if(code[i] == 'EXCEL_ERROR') {
return_message.push(Sprintf(General.lang('incorrect_excel_format'), value));
}
else if(code[i] == 'NUMERIC_ERROR') {
return_message.push(Sprintf(General.lang('the_content_must_be_a_number'), value));
}
else if(code[i] == 'GREATER_ERROR_0') {
return_message.push(Sprintf(General.lang('the_content_must_be_greater_than_0'), value));
}
else if(code[i] == 'GREATER_ERROR_-1') {
return_message.push(Sprintf(General.lang('the_content_must_be_greater_than_0'), value));
}
else if(code[i] == 'LESS_ERROR_100') {
return_message.push(Sprintf(General.lang('the_content_must_be_less_than_100'), value));
}
else if(code[i] == 'MIN_ERROR_10') {
return_message.push(Sprintf(General.lang('the_content_must_be_at_least_10_characters'), value));
}
else if(code[i] == 'EXIST_ERROR') {
return_message.push(Sprintf(General.lang('the_content_not_exist'), value));
}
else if(code[i] == 'STATUS_ERROR') {
return_message.push(Sprintf(General.lang('status_error'), value));
}
else if(code[i] == 'CURRENT_PASSWORD_NOT_MATCH') {
return_message.push(Sprintf(General.lang('current_password_not_match'), value));
}
else {
return_message.push(code[i]);
}
}
return return_message;
}
}
export default Form;
+626
View File
@@ -0,0 +1,626 @@
module.exports.getYear = function() {
let dt = new Date();
return dt.getFullYear();
};
module.exports.logout = function() {
localStorage.removeItem('user_detail');
localStorage.removeItem('user_jwt');
localStorage.removeItem('login_redirect');
window.location.replace(ENV.APP_URL + 'login');
};
module.exports.tableCall = function(url, form, recall = 0, path = 'list', append = true) {
if (recall <= 3) {
return form.get(
ENV.API_URL + url,
{
'Authorization': 'Bearer '+ TOKEN,
'App-Key': ENV.APP_KEY
}
).then(response => {
if (append) {
let query_string = response.query_string ? path + '?' + response.query_string : path;
window.history.replaceState('', '', query_string);
}
return response
})
.catch(function (error) {
if(error.response) {
if (recall >= 3 && error.response.status == 401) {
module.exports.logout();
}
console.log(recall++);
return module.exports.tableCall(url, form, recall++, path, append);
}
});
}
};
module.exports.getCall = function(url, form, recall = 0) {
if (recall <= 3) {
return form.get(
ENV.API_URL + url,
{
'Authorization': 'Bearer '+ TOKEN,
'App-Key': ENV.APP_KEY
}
).then(response => {
return response
})
.catch(function (error) {
if(error.response) {
if (recall >= 3 && error.response.status == 401) {
module.exports.logout();
}
console.log(recall++);
return module.exports.getCall(url, form, recall++);
}
});
}
};
module.exports.validCheck = function(url, form, ref_inputs) {
return new Promise((resolve, reject) => {
form.post(
ENV.API_URL + url,
{
'Authorization': 'Bearer '+ TOKEN,
'App-Key': ENV.APP_KEY
}
).then(data => {
resolve(true);
})
.catch(error => {
let ref_inputs_data = ref_inputs;
let error_status = true;
Object.keys(ref_inputs_data).forEach(function (key) {
if (form.errors.has(ref_inputs_data[key])) {
error_status = false;
}
});
if (!error_status) {
resolve(false);
}
else {
resolve(true);
form.errors.clear();
}
});
});
};
module.exports.onComplete = function(url, form, method) {
return new Promise((resolve, reject) => {
form[method](
ENV.API_URL + url,
{
'Authorization': 'Bearer '+ TOKEN,
'App-Key': ENV.APP_KEY
}
).then(data => {
resolve(data);
})
.catch(error => {
reject(false);
});
});
};
module.exports.getURLParam = function(key) {
const queryString = require('query-string');
const parsed = queryString.parse(location.search);
return parsed[key];
};
module.exports.getStringParam = function(key, url) {
url = url.split('?');
url = url[1] ? url[1] : url[0];
const queryString = require('query-string');
const parsed = queryString.parse('?' + url);
return parsed[key];
};
module.exports.getKey = function(set = [], type = 'key', null_type = 'text') {
let return_data = [];
if (set.length > 0) {
set.forEach(function(element, index) {
return_data.push(element[type] ? element[type] : element[null_type]);
});
}
return return_data;
};
module.exports.autoCompleteKeyConvert = function(set = [], tag = '') {
let return_date = [];
set.forEach(function(element, index) {
return_date.push({
'id': element.id,
'hash_id': element.hash_id,
'row': element,
'text': element.name,
'tiClasses':[ "ti-valid" ]
});
});
return return_date;
};
module.exports.initFormArray = function(set, form_field) {
let return_date = [];
if (form_field[0]) {
form_field.forEach(function(element, index) {
return_date.push(module.exports.filterByProperty(set, 'id', element))
});
}
return return_date;
};
module.exports.selectStructure = function(set = [], id = '', value = '', other = null, other_2 = null) {
let return_set = {
'data': [],
'actual_data': set
};
set.forEach(function(element, index) {
return_set.data.push({
'key': element[id],
'value': element[value] + ' ' + (other ? '(' + element[other][0].name + ')' : '') + ' ' + (other_2 ? '(' + element[other_2][0].name + ')' : '')
});
});
return return_set;
};
module.exports.returnAray = function(set) {
if (set !== undefined && set !== null) {
set = Array.isArray(set) ? set : [set];
}
else {
set = [];
}
return set;
};
// module.exports.postCall = function(url, params = {}) {
// return axios.post(
// ENV.API_URL + url, params, {
// headers: {
// 'Authorization': 'Bearer '+ TOKEN
// }
// }).then(response => {
// return response.data
// })
// .catch(function (error) {
// });
// };
// module.exports.putCall = function(url, params = {}) {
// return axios.put(
// ENV.API_URL + url, params, {
// headers: {
// 'Authorization': 'Bearer '+ TOKEN
// }
// }).then(response => {
// return response.data
// })
// .catch(function (error) {
// });
// };
// module.exports.setpUpdate = function(url, ref_inputs, form) {
// return new Promise((resolve, reject) => {
// form.put(
// ENV.API_URL + url, {
// headers: {
// 'Authorization': 'Bearer '+ TOKEN
// }
// }).then(data => {
// resolve(true);
// })
// .catch(error => {
// let ref_inputs_data = ref_inputs.split(',');
// let error_status = true;
// Object.keys(ref_inputs_data).forEach(function (key) {
// // variation_rule
// if(typeof form[ref_inputs_data[key]] === 'object') {
// if (ref_inputs_data[key] == 'variation_rule' && form[ref_inputs_data[key]].type) {
// if (form[ref_inputs_data[key]].type == 1) {
// let set = form[ref_inputs_data[key]].content.detail;
// set.forEach(function(element, index) {
// let set_key = Object.keys(element);
// set_key.forEach(function(element_2, index_2) {
// if (form.errors.has(ref_inputs_data[key] + '.content.detail.' + index + '.' + element_2)) {
// error_status = false;
// }
// });
// });
// }
// else {
// let set = form[ref_inputs_data[key]].content.detail;
// set.forEach(function(element, index) {
// let set_2 = element.content.detail;
// set_2.forEach(function(element_2, index_2) {
// let set_key = Object.keys(element_2);
// set_key.forEach(function(element_3, index_3) {
// if (form.errors.has(ref_inputs_data[key] + '.content.detail.' + index + '.content.detail.' + index_2 + '.' + element_3)) {
// error_status = false;
// }
// });
// });
// });
// }
// }
// }
// if (Array.isArray(form[ref_inputs_data[key]])) {
// let set = form[ref_inputs_data[key]];
// set.forEach(function(element, index) {
// let set_key = Object.keys(element);
// set_key.forEach(function(element_2, index_2) {
// if (form.errors.has(ref_inputs_data[key] + '.' + index + '.' + element_2)) {
// error_status = false;
// }
// });
// });
// }
// else {
// if (form.errors.has(ref_inputs_data[key])) {
// error_status = false;
// }
// }
// });
// if (!error_status) {
// resolve(false);
// }
// else {
// resolve(true);
// form.errors.clear();
// }
// });
// });
// };
module.exports.filterByProperty = function(array, prop, value) {
for(var i = 0; i < array.length; i++){
var obj = array[i];
for(var key in obj){
if(typeof(obj[key] == "object")){
var item = obj[key];
if(key == prop && item == value){
return obj;
}
}
}
}
};
module.exports.arrayConvert = function(set = []) {
let return_date = [];
set.forEach(function(element, index) {
return_date.push({
'id': element,
'name': element
});
});
return return_date;
};
module.exports.imageBase64 = function(imgs) {
let num = 1;
let base = [];
let count = imgs.length;
return new Promise((resolve, reject) => {
Array.from(imgs).forEach(file => {
let reader = new FileReader();
reader.onload = () => {
base.push(reader.result);
if (num >= count) {
resolve(base);
}
num++;
};
reader.readAsDataURL(file);
})
});
};
module.exports.toastedMessage = function(type, object, key) {
let message = '';
let value = [
{att: object},
{att: key},
];
if (type == 'create') {
message = Sprintf(module.exports.lang('have_been_successfully_created'), {value: value});
}
else if(type == 'edit') {
message = Sprintf(module.exports.lang('have_been_successfully_updated'), {value: value});
}
Vue.toasted.success(message, {
icon : {
name : 'check',
}
}).goAway(3500);
};
module.exports.lang = function(code) {
let lang_selected = localStorage.getItem('lang_selected');
if (lang_selected == 'cn') {
return Cn[code];
}
else {
return En[code];
}
};
module.exports.selectOptionConvert = function(set = [], id = '', value = '', other = null, other_2 = null) {
let return_set = [];
set.forEach(function(element, index) {
return_set.push({
'key': element[id],
'value': element[value] + ' ' + (other ? '(' + element[other][0].name + ')' : '') + ' ' + (other_2 ? '(' + element[other_2][0].name + ')' : '')
});
});
return return_set;
};
module.exports.filterOut = function(set = [], out = []) {
let filter_set = [];
set.forEach(function(element, index) {
let allow = true;
out.forEach(function(element_out, index_out) {
if (element.id == element_out) {
allow = false;
}
});
if (allow == true) {
filter_set.push(element);
}
});
return filter_set;
};
module.exports.videoBase64 = function(video) {
let num = 1;
let base = [];
let count = video.length;
return new Promise((resolve, reject) => {
Array.from(video).forEach(file => {
let reader = new FileReader();
reader.onload = () => {
base.push(reader.result);
if (num >= count) {
resolve(base);
}
num++;
};
reader.readAsDataURL(file);
})
});
};
module.exports.errorData = function(data) {
let error_data = [];
let error_message = '';
Object.keys(data).forEach(function(key) {
error_data.push(data[key]);
});
error_message = error_data.join('<br>');
return error_message;
};
module.exports.timeSince = function(date) {
var seconds = Math.floor((new Date() - date) / 1000);
var interval = Math.floor(seconds / 31536000);
if (interval > 1) {
return 'Active ' + interval + ' Years Ago';
}
interval = (seconds / 2592000);
if (interval > 1) {
return 'Active ' + Math.floor(interval) + ' Months Ago';
}
interval = (seconds / 86400);
if (interval > 1) {
return 'Active ' + Math.floor(interval) + ' Days Ago';
}
interval = (seconds / 3600);
if (interval > 1) {
return 'Active' + Math.floor(interval) + ' Hours Ago';
}
interval = (seconds / 60);
if (interval > 1) {
return 'Active ' + Math.floor(interval) + ' Minutes Ago';
}
return 'Active ' + Math.floor(seconds) + ' Seconds Ago';
};
module.exports.shortenNumber = function(num) {
let nformat = 0;
if (num < 1000) {
nformat = number_format(num);
}
else if (num < 1000000) {
// Anything less than a million
nformat = (num / 1000).toFixed(1) + 'k';
}
else if (num < 1000000000) {
// Anything less than a billion
nformat = (num / 1000000).toFixed(3) + 'm';
}
else {
// At least a billion
nformat = (num / 1000000000, 3).toFixed(3) + 'b';
}
return nformat;
};
// public static function shortenNumber($number){
// if ($number < 1000) {
// $nformat = number_format($number);
// }else if ($number < 1000000) {
// // Anything less than a million
// $nformat = number_format($number/1000,1) . 'k';
// } else if ($number < 1000000000) {
// // Anything less than a billion
// $nformat = number_format($number / 1000000, 3) . 'm';
// } else {
// // At least a billion
// $nformat = number_format($number / 1000000000, 3) . 'b';
// }
// return $nformat;
// }
// module.exports.selectStructureNonDB = function(set = []) {
// let return_set = {
// 'data': [],
// 'actual_data': set.data
// };
// set.data.forEach(function(element, index) {
// return_set.data.push({
// 'key': element,
// 'value': element
// });
// });
// return return_set;
// };
module.exports.httpConvert = function(params) {
let query_string = '';
Object.keys(params).forEach(function(element, index) {
if( Object.prototype.toString.call( params[element] ) === '[object Array]' ) {
if (params[element][0]) {
params[element].forEach(function(element_2, index_2) {
query_string += element + '[]=' + element_2 + '&';
});
}
}
else {
if (params[element]) {
console.log(params[element]);
query_string += element + '=' + params[element] + '&';
}
}
});
return query_string;
};
// return filter_set;
// };
// module.exports.pluckID = function(set = []) {
// let return_set = [];
// set.forEach(function(element, index) {
// return_set.push(element.id);
// });
// return return_set;
// };
// module.exports.dateConvert = function(date) {
// let return_date = '';
// if (date) {
// let parts = date.split('-');
// return_date = new Date(parts[2], parts[1] - 1, parts[0]);
// }
// return return_date;
// };
// module.exports.foreachErrorToasted = function(data) {
// let error_data = module.exports.errorData(data);
// error_data.forEach(function(element, index) {
// Vue.toasted.error(element, {
// icon : {
// name : 'close',
// }
// }).goAway(3500);
// });
// };
// module.exports.getURLParam = function(key) {
// const queryString = require('query-string');
// const parsed = queryString.parse(location.search);
// return parsed[key];
// };
// module.exports.returnAray = function(set) {
// if (set !== undefined) {
// set = Array.isArray(set) ? set : [set];
// }
// else {
// set = [];
// }
// return set;
// };
// module.exports.autoCompleteKeyConvert = function(set = []) {
// let return_date = [];
// set.forEach(function(element, index) {
// return_date.push({
// 'id': element.id,
// 'row': element,
// 'text': element.name,
// 'tiClasses':[ "ti-valid" ]
// });
// });
// return return_date;
// };
// module.exports.formatNumber = function(set, form_field) {
// if (form_field[0]) {
// form_field.forEach(function(num) {
// return num.toString().replace(/(\d)(?=(\d{3})+(?!\d))/g, "$1,");
// });
// }
// };
+83
View File
@@ -0,0 +1,83 @@
module.exports.menuDefine = function(permission = []) {
let menu = [];
menu.push({
header: true,
title: 'Main Navigation',
});
/////////////////////////////////////////////////////
menu.push({
title: 'Dashboard',
icon: 'fa fa-home',
child: [
{
href: '/',
title: 'Analytics',
},
]
});
/////////////////////////////////////////////////////
if (permission.includes('view user')) {
let admin_access_menu = {};
admin_access_menu = {
title: 'User',
icon: 'fa fa-user',
child: [
{
href: '/user/list',
title: 'List',
},
]
};
menu.push(admin_access_menu);
}
/////////////////////////////////////////////////////
if (permission.includes('view admin')) {
let admin_access_menu = {};
admin_access_menu = {
title: 'Admin',
icon: 'fa fa-user-astronaut',
child: [
{
href: '/admin/list',
title: 'List',
},
]
};
if (permission.includes('add admin')) {
admin_access_menu.child.push(
{
href: '/admin/create',
title: 'Create',
}
);
}
menu.push(admin_access_menu);
}
/////////////////////////////////////////////////////
if (permission.includes('view role')) {
let admin_access_menu = {};
admin_access_menu = {
title: 'Role',
icon: 'fa fa-user-tag',
child: [
{
href: '/admin-role/list',
title: 'Admin List',
},
{
href: '/reviewer-role/list',
title: 'Reviewer List',
},
]
};
menu.push(admin_access_menu);
}
/////////////////////////////////////////////////////
return menu;
};
+1 -1
View File
@@ -210,7 +210,7 @@
}
.big-logo {
width: 250px;
width: 100px;
}
.modal-footer {
+142
View File
@@ -0,0 +1,142 @@
// Applications
$app-layout-sidebar-width: 270px;
$app-layout-aside-width: 60px;
$app-layout-header-height: 50px;
.app-inner-layout {
&.rm-sidebar {
.app-inner-layout__wrapper {
.app-inner-layout__content {
width: auto;
float: none;
}
}
}
.app-inner-layout__header {
width: 100%;
padding: $layout-spacer-x;
text-align: left;
.app-page-title {
margin: 0;
padding: 0;
background: transparent;
}
border-bottom: $gray-200 solid 1px;
}
.app-inner-layout__header-boxed {
padding: $layout-spacer-x;
.app-inner-layout__header {
@include border-radius($border-radius-lg);
}
}
.app-inner-layout__wrapper {
position: relative;
display: flex;
flex-direction: row;
min-height: 100vh;
.app-inner-layout__content {
flex: 1;
display: flex;
&.card {
box-shadow: 0 0 0 0 transparent;
border-radius: 0;
border: 0;
}
.app-inner-layout__top-pane {
display: flex;
align-content: center;
align-items: center;
text-align: left;
padding: ($layout-spacer-x / 2) $layout-spacer-x;
}
.pane-left {
display: flex;
align-items: center;
}
.pane-right {
display: flex;
align-items: center;
margin-left: auto;
}
.app-inner-layout__bottom-pane {
padding: $layout-spacer-x;
border-top: $gray-200 solid 1px;
}
}
.app-inner-layout__sidebar {
width: $app-layout-sidebar-width;
list-style: none;
text-align: left;
order: -1;
flex: 0 0 $app-layout-sidebar-width;
display: flex;
margin: 0;
position: relative;
.dropdown-item {
white-space: normal;
}
&.card {
box-shadow: 0 0 0 0 transparent;
border-radius: 0;
background: $gray-100;
border: 0;
border-right: $gray-200 solid 1px;
border-left: $gray-200 solid 1px;
}
.app-inner-layout__sidebar-footer,
.app-inner-layout__sidebar-header {
background: $gray-100;
}
}
.app-inner-layout__aside {
width: $app-layout-aside-width;
}
}
.app-inner-layout__footer {
width: 100%;
height: $app-layout-header-height;
}
}
.app-wrapper-footer {
.app-footer {
border-top: $gray-200 solid 1px;
.app-footer__inner {
border-left: $gray-200 solid 1px;
}
}
}
// Components
@import "chat";
// Responsive
.mobile-app-menu-btn {
display: none;
margin: 3px $layout-spacer-x 0 0;
}
@import "responsive";
+35
View File
@@ -0,0 +1,35 @@
// Chat
$app-layout-chat-sidebar-width: 360px;
.chat-layout {
&.app-inner-layout {
.app-inner-layout__sidebar {
width: $app-layout-chat-sidebar-width;
flex: 0 0 $app-layout-chat-sidebar-width;
}
}
.app-inner-layout__top-pane h4 {
font-size: $h5-font-size;
div {
font-size: $font-size-base;
}
}
.chat-box-wrapper {
padding: $layout-spacer-x;
}
}
@include media-breakpoint-down(lg) {
.chat-layout {
&.app-inner-layout {
.app-inner-layout__sidebar {
.widget-content .widget-content-left .widget-subheading {
white-space: normal;
}
}
}
}
}
+18
View File
@@ -0,0 +1,18 @@
// Responsive Applications
@include media-breakpoint-down(md) {
.app-inner-layout__sidebar {
display: none !important;
}
.mobile-app-menu-btn {
display: block;
}
.open-mobile-menu {
.app-inner-layout__sidebar {
display: block !important;
}
}
}
+51
View File
@@ -0,0 +1,51 @@
//
// Base styles
//
.alert {
position: relative;
padding: $alert-padding-y $alert-padding-x;
margin-bottom: $alert-margin-bottom;
border: $alert-border-width solid transparent;
@include border-radius($alert-border-radius);
}
// Headings for larger alerts
.alert-heading {
// Specified to prevent conflicts of changing $headings-color
color: inherit;
}
// Provide class for links that match alerts
.alert-link {
font-weight: $alert-link-font-weight;
}
// Dismissible alerts
//
// Expand the right padding and account for the close button's positioning.
.alert-dismissible {
padding-right: $close-font-size + $alert-padding-x * 2;
// Adjust close link position
.close {
position: absolute;
top: 0;
right: 0;
padding: $alert-padding-y $alert-padding-x;
color: inherit;
}
}
// Alternate styles
//
// Generate contextual modifier classes for colorizing the alert.
@each $color, $value in $theme-colors {
.alert-#{$color} {
@include alert-variant(theme-color-level($color, $alert-bg-level), theme-color-level($color, $alert-border-level), theme-color-level($color, $alert-color-level));
}
}
+53
View File
@@ -0,0 +1,53 @@
// Base class
//
// Requires one of the contextual, color modifier classes for `color` and
// `background-color`.
.badge {
display: inline-block;
padding: $badge-padding-y $badge-padding-x;
font-size: $badge-font-size;
font-weight: $badge-font-weight;
line-height: 1;
text-align: center;
white-space: nowrap;
vertical-align: baseline;
@include border-radius($badge-border-radius);
@at-root a#{&} {
@include hover-focus {
text-decoration: none;
}
}
// Empty badges collapse automatically
&:empty {
display: none;
}
}
// Quick fix for badges in buttons
.btn .badge {
position: relative;
top: -1px;
}
// Pill badges
//
// Make them extra rounded with a modifier to replace v3's badges.
.badge-pill {
padding-right: $badge-pill-padding-x;
padding-left: $badge-pill-padding-x;
@include border-radius($badge-pill-border-radius);
}
// Colors
//
// Contextual variations (linked badges get darker on :hover).
@each $color, $value in $theme-colors {
.badge-#{$color} {
@include badge-variant($value);
}
}
@@ -0,0 +1,41 @@
.breadcrumb {
display: flex;
flex-wrap: wrap;
padding: $breadcrumb-padding-y $breadcrumb-padding-x;
margin-bottom: $breadcrumb-margin-bottom;
list-style: none;
background-color: $breadcrumb-bg;
@include border-radius($breadcrumb-border-radius);
}
.breadcrumb-item {
// The separator between breadcrumbs (by default, a forward-slash: "/")
+ .breadcrumb-item {
padding-left: $breadcrumb-item-padding;
&::before {
display: inline-block; // Suppress underlining of the separator in modern browsers
padding-right: $breadcrumb-item-padding;
color: $breadcrumb-divider-color;
content: $breadcrumb-divider;
}
}
// IE9-11 hack to properly handle hyperlink underlines for breadcrumbs built
// without `<ul>`s. The `::before` pseudo-element generates an element
// *within* the .breadcrumb-item and thereby inherits the `text-decoration`.
//
// To trick IE into suppressing the underline, we give the pseudo-element an
// underline and then immediately remove it.
+ .breadcrumb-item:hover::before {
text-decoration: underline;
}
// stylelint-disable-next-line no-duplicate-selectors
+ .breadcrumb-item:hover::before {
text-decoration: none;
}
&.active {
color: $breadcrumb-active-color;
}
}
@@ -0,0 +1,163 @@
// stylelint-disable selector-no-qualifying-type
// Make the div behave like a button
.btn-group,
.btn-group-vertical {
position: relative;
display: inline-flex;
vertical-align: middle; // match .btn alignment given font-size hack above
> .btn {
position: relative;
flex: 1 1 auto;
// Bring the hover, focused, and "active" buttons to the front to overlay
// the borders properly
@include hover {
z-index: 1;
}
&:focus,
&:active,
&.active {
z-index: 1;
}
}
}
// Optional: Group multiple button groups together for a toolbar
.btn-toolbar {
display: flex;
flex-wrap: wrap;
justify-content: flex-start;
.input-group {
width: auto;
}
}
.btn-group {
// Prevent double borders when buttons are next to each other
> .btn:not(:first-child),
> .btn-group:not(:first-child) {
margin-left: -$btn-border-width;
}
// Reset rounded corners
> .btn:not(:last-child):not(.dropdown-toggle),
> .btn-group:not(:last-child) > .btn {
@include border-right-radius(0);
}
> .btn:not(:first-child),
> .btn-group:not(:first-child) > .btn {
@include border-left-radius(0);
}
}
// Sizing
//
// Remix the default button sizing classes into new ones for easier manipulation.
.btn-group-sm > .btn { @extend .btn-sm; }
.btn-group-lg > .btn { @extend .btn-lg; }
//
// Split button dropdowns
//
.dropdown-toggle-split {
padding-right: $btn-padding-x * .75;
padding-left: $btn-padding-x * .75;
&::after,
.dropup &::after,
.dropright &::after {
margin-left: 0;
}
.dropleft &::before {
margin-right: 0;
}
}
.btn-sm + .dropdown-toggle-split {
padding-right: $btn-padding-x-sm * .75;
padding-left: $btn-padding-x-sm * .75;
}
.btn-lg + .dropdown-toggle-split {
padding-right: $btn-padding-x-lg * .75;
padding-left: $btn-padding-x-lg * .75;
}
// The clickable button for toggling the menu
// Set the same inset shadow as the :active state
.btn-group.show .dropdown-toggle {
@include box-shadow($btn-active-box-shadow);
// Show no shadow for `.btn-link` since it has no other button styles.
&.btn-link {
@include box-shadow(none);
}
}
//
// Vertical button groups
//
.btn-group-vertical {
flex-direction: column;
align-items: flex-start;
justify-content: center;
> .btn,
> .btn-group {
width: 100%;
}
> .btn:not(:first-child),
> .btn-group:not(:first-child) {
margin-top: -$btn-border-width;
}
// Reset rounded corners
> .btn:not(:last-child):not(.dropdown-toggle),
> .btn-group:not(:last-child) > .btn {
@include border-bottom-radius(0);
}
> .btn:not(:first-child),
> .btn-group:not(:first-child) > .btn {
@include border-top-radius(0);
}
}
// Checkbox and radio options
//
// In order to support the browser's form validation feedback, powered by the
// `required` attribute, we have to "hide" the inputs via `clip`. We cannot use
// `display: none;` or `visibility: hidden;` as that also hides the popover.
// Simply visually hiding the inputs via `opacity` would leave them clickable in
// certain cases which is prevented by using `clip` and `pointer-events`.
// This way, we ensure a DOM element is visible to position the popover from.
//
// See https://github.com/twbs/bootstrap/pull/12794 and
// https://github.com/twbs/bootstrap/pull/14559 for more information.
.btn-group-toggle {
> .btn,
> .btn-group > .btn {
margin-bottom: 0; // Override default `<label>` value
input[type="radio"],
input[type="checkbox"] {
position: absolute;
clip: rect(0, 0, 0, 0);
pointer-events: none;
}
}
}
+140
View File
@@ -0,0 +1,140 @@
// stylelint-disable selector-no-qualifying-type
//
// Base styles
//
.btn {
display: inline-block;
font-weight: $btn-font-weight;
color: $body-color;
text-align: center;
vertical-align: middle;
user-select: none;
background-color: transparent;
border: $btn-border-width solid transparent;
@include button-size($btn-padding-y, $btn-padding-x, $btn-font-size, $btn-line-height, $btn-border-radius);
@include transition($btn-transition);
@include hover {
color: $body-color;
text-decoration: none;
}
&:focus,
&.focus {
outline: 0;
box-shadow: $btn-focus-box-shadow;
}
// Disabled comes first so active can properly restyle
&.disabled,
&:disabled {
opacity: $btn-disabled-opacity;
@include box-shadow(none);
}
// Opinionated: add "hand" cursor to non-disabled .btn elements
&:not(:disabled):not(.disabled) {
cursor: pointer;
}
&:not(:disabled):not(.disabled):active,
&:not(:disabled):not(.disabled).active {
@include box-shadow($btn-active-box-shadow);
&:focus {
@include box-shadow($btn-focus-box-shadow, $btn-active-box-shadow);
}
}
}
// Future-proof disabling of clicks on `<a>` elements
a.btn.disabled,
fieldset:disabled a.btn {
pointer-events: none;
}
//
// Alternate buttons
//
@each $color, $value in $theme-colors {
.btn-#{$color} {
@include button-variant($value, $value);
}
}
@each $color, $value in $theme-colors {
.btn-outline-#{$color} {
@include button-outline-variant($value);
}
}
//
// Link buttons
//
// Make a button look and behave like a link
.btn-link {
font-weight: $font-weight-normal;
color: $link-color;
@include hover {
color: $link-hover-color;
text-decoration: $link-hover-decoration;
}
&:focus,
&.focus {
text-decoration: $link-hover-decoration;
box-shadow: none;
}
&:disabled,
&.disabled {
color: $btn-link-disabled-color;
pointer-events: none;
}
// No need for an active state here
}
//
// Button Sizes
//
.btn-lg {
@include button-size($btn-padding-y-lg, $btn-padding-x-lg, $btn-font-size-lg, $btn-line-height-lg, $btn-border-radius-lg);
}
.btn-sm {
@include button-size($btn-padding-y-sm, $btn-padding-x-sm, $btn-font-size-sm, $btn-line-height-sm, $btn-border-radius-sm);
}
//
// Block button
//
.btn-block {
display: block;
width: 100%;
// Vertically space out multiple block buttons
+ .btn-block {
margin-top: $btn-block-spacing-y;
}
}
// Specificity overrides
input[type="submit"],
input[type="reset"],
input[type="button"] {
&.btn-block {
width: 100%;
}
}
+325
View File
@@ -0,0 +1,325 @@
//
// Base styles
//
.card {
position: relative;
display: flex;
flex-direction: column;
min-width: 0;
word-wrap: break-word;
background-color: $card-bg;
background-clip: border-box;
border: $card-border-width solid $card-border-color;
@include border-radius($card-border-radius);
> hr {
margin-right: 0;
margin-left: 0;
}
> .list-group:first-child {
.list-group-item:first-child {
@include border-top-radius($card-border-radius);
}
}
> .list-group:last-child {
.list-group-item:last-child {
@include border-bottom-radius($card-border-radius);
}
}
}
.card-body {
// Enable `flex-grow: 1` for decks and groups so that card blocks take up
// as much space as possible, ensuring footers are aligned to the bottom.
flex: 1 1 auto;
padding: $card-spacer-x;
}
.card-title {
margin-bottom: $card-spacer-y;
}
.card-subtitle {
margin-top: -$card-spacer-y / 2;
margin-bottom: 0;
}
.card-text:last-child {
margin-bottom: 0;
}
.card-link {
@include hover {
text-decoration: none;
}
+ .card-link {
margin-left: $card-spacer-x;
}
}
//
// Optional textual caps
//
.card-header {
padding: $card-spacer-y $card-spacer-x;
margin-bottom: 0; // Removes the default margin-bottom of <hN>
color: $card-cap-color;
background-color: $card-cap-bg;
border-bottom: $card-border-width solid $card-border-color;
&:first-child {
@include border-radius($card-inner-border-radius $card-inner-border-radius 0 0);
}
+ .list-group {
.list-group-item:first-child {
border-top: 0;
}
}
}
.card-footer {
padding: $card-spacer-y $card-spacer-x;
background-color: $card-cap-bg;
border-top: $card-border-width solid $card-border-color;
&:last-child {
@include border-radius(0 0 $card-inner-border-radius $card-inner-border-radius);
}
}
//
// Header navs
//
.card-header-tabs.nav-tabs {
margin-right: -$card-spacer-x / 2;
margin-bottom: 0;
margin-left: -$card-spacer-x / 2;
border-bottom: 0;
.nav-link {
border: 0;
}
}
.card-header-pills.nav-pills {
margin-right: -$card-spacer-x / 2;
margin-left: -$card-spacer-x / 2;
margin-bottom: 0;
.nav-link {
&::before {
display: none;
}
&.active {
color: $white !important;
}
}
}
// Card image
.card-img-overlay {
position: absolute;
top: 0;
right: 0;
bottom: 0;
left: 0;
padding: $card-img-overlay-padding;
}
.card-img {
width: 100%; // Required because we use flexbox and this inherently applies align-self: stretch
@include border-radius($card-inner-border-radius);
}
// Card image caps
.card-img-top {
width: 100%; // Required because we use flexbox and this inherently applies align-self: stretch
@include border-top-radius($card-inner-border-radius);
}
.card-img-bottom {
width: 100%; // Required because we use flexbox and this inherently applies align-self: stretch
@include border-bottom-radius($card-inner-border-radius);
}
// Card deck
.card-deck {
display: flex;
flex-direction: column;
.card {
margin-bottom: $card-deck-margin;
}
@include media-breakpoint-up(sm) {
flex-flow: row wrap;
margin-right: -$card-deck-margin;
margin-left: -$card-deck-margin;
.card {
display: flex;
// Flexbugs #4: https://github.com/philipwalton/flexbugs#flexbug-4
flex: 1 0 0%;
flex-direction: column;
margin-right: $card-deck-margin;
margin-bottom: 0; // Override the default
margin-left: $card-deck-margin;
}
}
}
//
// Card groups
//
.card-group {
display: flex;
flex-direction: column;
// The child selector allows nested `.card` within `.card-group`
// to display properly.
> .card {
margin-bottom: $card-group-margin;
}
@include media-breakpoint-up(sm) {
flex-flow: row wrap;
// The child selector allows nested `.card` within `.card-group`
// to display properly.
> .card {
// Flexbugs #4: https://github.com/philipwalton/flexbugs#flexbug-4
flex: 1 0 0%;
margin-bottom: 0;
+ .card {
margin-left: 0;
border-left: 0;
}
// Handle rounded corners
@if $enable-rounded {
&:first-child {
@include border-right-radius(0);
.card-img-top,
.card-header {
border-top-right-radius: 0;
}
.card-img-bottom,
.card-footer {
border-bottom-right-radius: 0;
}
}
&:last-child {
@include border-left-radius(0);
.card-img-top,
.card-header {
border-top-left-radius: 0;
}
.card-img-bottom,
.card-footer {
border-bottom-left-radius: 0;
}
}
&:only-child {
@include border-radius($card-border-radius);
.card-img-top,
.card-header {
@include border-top-radius($card-border-radius);
}
.card-img-bottom,
.card-footer {
@include border-bottom-radius($card-border-radius);
}
}
&:not(:first-child):not(:last-child):not(:only-child) {
@include border-radius(0);
.card-img-top,
.card-img-bottom,
.card-header,
.card-footer {
@include border-radius(0);
}
}
}
}
}
}
//
// Columns
//
.card-columns {
.card {
margin-bottom: $card-columns-margin;
}
@include media-breakpoint-up(sm) {
column-count: $card-columns-count;
column-gap: $card-columns-gap;
orphans: 1;
widows: 1;
.card {
display: inline-block; // Don't let them vertically span multiple columns
width: 100%; // Don't let their width change
}
}
}
//
// Accordion
//
.accordion {
.card {
overflow: hidden;
&:not(:first-of-type) {
.card-header:first-child {
border-radius: 0;
}
&:not(:last-of-type) {
border-bottom: 0;
border-radius: 0;
}
}
&:first-of-type {
border-bottom: 0;
border-bottom-right-radius: 0;
border-bottom-left-radius: 0;
}
&:last-of-type {
border-top-left-radius: 0;
border-top-right-radius: 0;
}
.card-header {
margin-bottom: -$card-border-width;
}
}
}
+198
View File
@@ -0,0 +1,198 @@
// Notes on the classes:
//
// 1. .carousel.pointer-event should ideally be pan-y (to allow for users to scroll vertically)
// even when their scroll action started on a carousel, but for compatibility (with Firefox)
// we're preventing all actions instead
// 2. The .carousel-item-left and .carousel-item-right is used to indicate where
// the active slide is heading.
// 3. .active.carousel-item is the current slide.
// 4. .active.carousel-item-left and .active.carousel-item-right is the current
// slide in its in-transition state. Only one of these occurs at a time.
// 5. .carousel-item-next.carousel-item-left and .carousel-item-prev.carousel-item-right
// is the upcoming slide in transition.
.carousel {
position: relative;
}
.carousel.pointer-event {
touch-action: pan-y;
}
.carousel-inner {
position: relative;
width: 100%;
overflow: hidden;
@include clearfix();
}
.carousel-item {
position: relative;
display: none;
float: left;
width: 100%;
margin-right: -100%;
backface-visibility: hidden;
@include transition($carousel-transition);
}
.carousel-item.active,
.carousel-item-next,
.carousel-item-prev {
display: block;
}
.carousel-item-next:not(.carousel-item-left),
.active.carousel-item-right {
transform: translateX(100%);
}
.carousel-item-prev:not(.carousel-item-right),
.active.carousel-item-left {
transform: translateX(-100%);
}
//
// Alternate transitions
//
.carousel-fade {
.carousel-item {
opacity: 0;
transition-property: opacity;
transform: none;
}
.carousel-item.active,
.carousel-item-next.carousel-item-left,
.carousel-item-prev.carousel-item-right {
z-index: 1;
opacity: 1;
}
.active.carousel-item-left,
.active.carousel-item-right {
z-index: 0;
opacity: 0;
@include transition(0s $carousel-transition-duration opacity);
}
}
//
// Left/right controls for nav
//
.carousel-control-prev,
.carousel-control-next {
position: absolute;
top: 0;
bottom: 0;
z-index: 1;
// Use flex for alignment (1-3)
display: flex; // 1. allow flex styles
align-items: center; // 2. vertically center contents
justify-content: center; // 3. horizontally center contents
width: $carousel-control-width;
color: $carousel-control-color;
text-align: center;
opacity: $carousel-control-opacity;
@include transition($carousel-control-transition);
// Hover/focus state
@include hover-focus {
color: $carousel-control-color;
text-decoration: none;
outline: 0;
opacity: $carousel-control-hover-opacity;
}
}
.carousel-control-prev {
left: 0;
@if $enable-gradients {
background: linear-gradient(90deg, rgba($black, .25), rgba($black, .001));
}
}
.carousel-control-next {
right: 0;
@if $enable-gradients {
background: linear-gradient(270deg, rgba($black, .25), rgba($black, .001));
}
}
// Icons for within
.carousel-control-prev-icon,
.carousel-control-next-icon {
display: inline-block;
width: $carousel-control-icon-width;
height: $carousel-control-icon-width;
background: transparent no-repeat center center;
background-size: 100% 100%;
}
.carousel-control-prev-icon {
background-image: $carousel-control-prev-icon-bg;
}
.carousel-control-next-icon {
background-image: $carousel-control-next-icon-bg;
}
// Optional indicator pips
//
// Add an ordered list with the following class and add a list item for each
// slide your carousel holds.
.carousel-indicators {
position: absolute;
right: 0;
bottom: 0;
left: 0;
z-index: 15;
display: flex;
justify-content: center;
padding-left: 0; // override <ol> default
// Use the .carousel-control's width as margin so we don't overlay those
margin-right: $carousel-control-width;
margin-left: $carousel-control-width;
list-style: none;
li {
box-sizing: content-box;
flex: 0 1 auto;
width: $carousel-indicator-width;
height: $carousel-indicator-height;
margin-right: $carousel-indicator-spacer;
margin-left: $carousel-indicator-spacer;
text-indent: -999px;
cursor: pointer;
background-color: $carousel-indicator-active-bg;
background-clip: padding-box;
// Use transparent borders to increase the hit area by 10px on top and bottom.
border-top: $carousel-indicator-hit-area-height solid transparent;
border-bottom: $carousel-indicator-hit-area-height solid transparent;
opacity: .5;
@include transition($carousel-indicator-transition);
}
.active {
opacity: 1;
}
}
// Optional captions
//
//
.carousel-caption {
position: absolute;
right: (100% - $carousel-caption-width) / 2;
bottom: 20px;
left: (100% - $carousel-caption-width) / 2;
z-index: 10;
padding-top: 20px;
padding-bottom: 20px;
color: $carousel-caption-color;
text-align: center;
}
+44
View File
@@ -0,0 +1,44 @@
.close {
float: right;
font-size: $close-font-size;
font-weight: $close-font-weight;
line-height: 1;
color: $close-color;
text-shadow: $close-text-shadow;
opacity: .5;
// Override <a>'s hover style
@include hover {
color: $close-color;
text-decoration: none;
}
&:not(:disabled):not(.disabled) {
@include hover-focus {
opacity: .75;
}
// Opinionated: add "hand" cursor to non-disabled .close elements
cursor: pointer;
}
}
// Additional properties for button version
// iOS requires the button element instead of an anchor tag.
// If you want the anchor version, it requires `href="#"`.
// See https://developer.mozilla.org/en-US/docs/Web/Events/click#Safari_Mobile
// stylelint-disable-next-line selector-no-qualifying-type
button.close {
padding: 0;
background-color: transparent;
border: 0;
appearance: none;
}
// Future-proof disabling of clicks on `<a>` elements
// stylelint-disable-next-line selector-no-qualifying-type
a.close.disabled {
pointer-events: none;
}
+48
View File
@@ -0,0 +1,48 @@
// Inline code
code {
font-size: $code-font-size;
color: $code-color;
word-break: break-word;
// Streamline the style when inside anchors to avoid broken underline and more
a > & {
color: inherit;
}
}
// User input typically entered via keyboard
kbd {
padding: $kbd-padding-y $kbd-padding-x;
font-size: $kbd-font-size;
color: $kbd-color;
background-color: $kbd-bg;
@include border-radius($border-radius-sm);
@include box-shadow($kbd-box-shadow);
kbd {
padding: 0;
font-size: 100%;
font-weight: $nested-kbd-font-weight;
@include box-shadow(none);
}
}
// Blocks of code
pre {
display: block;
font-size: $code-font-size;
color: $pre-color;
// Account for some code outputs that place code tags in pre tags
code {
font-size: inherit;
color: inherit;
word-break: normal;
}
}
// Enable scrollable blocks of code
.pre-scrollable {
max-height: $pre-scrollable-max-height;
overflow-y: scroll;
}
@@ -0,0 +1,507 @@
// Embedded icons from Open Iconic.
// Released under MIT and copyright 2014 Waybury.
// https://useiconic.com/open
// Checkboxes and radios
//
// Base class takes care of all the key behavioral aspects.
.custom-control {
position: relative;
display: block;
min-height: $font-size-base * $line-height-base;
padding-left: $custom-control-gutter + $custom-control-indicator-size;
}
.custom-control-inline {
display: inline-flex;
margin-right: $custom-control-spacer-x;
}
.custom-control-input {
position: absolute;
z-index: -1; // Put the input behind the label so it doesn't overlay text
opacity: 0;
&:checked ~ .custom-control-label::before {
color: $custom-control-indicator-checked-color;
border-color: $custom-control-indicator-checked-border-color;
@include gradient-bg($custom-control-indicator-checked-bg);
@include box-shadow($custom-control-indicator-checked-box-shadow);
}
&:focus ~ .custom-control-label::before {
// the mixin is not used here to make sure there is feedback
@if $enable-shadows {
box-shadow: $input-box-shadow, $input-focus-box-shadow;
} @else {
box-shadow: $custom-control-indicator-focus-box-shadow;
}
}
&:focus:not(:checked) ~ .custom-control-label::before {
border-color: $custom-control-indicator-focus-border-color;
}
&:not(:disabled):active ~ .custom-control-label::before {
color: $custom-control-indicator-active-color;
background-color: $custom-control-indicator-active-bg;
border-color: $custom-control-indicator-active-border-color;
@include box-shadow($custom-control-indicator-active-box-shadow);
}
&:disabled {
~ .custom-control-label {
color: $custom-control-label-disabled-color;
&::before {
background-color: $custom-control-indicator-disabled-bg;
}
}
}
}
// Custom control indicators
//
// Build the custom controls out of pseudo-elements.
.custom-control-label {
position: relative;
margin-bottom: 0;
vertical-align: top;
// Background-color and (when enabled) gradient
&::before {
position: absolute;
top: ($font-size-base * $line-height-base - $custom-control-indicator-size) / 2;
left: -($custom-control-gutter + $custom-control-indicator-size);
display: block;
width: $custom-control-indicator-size;
height: $custom-control-indicator-size;
pointer-events: none;
content: "";
background-color: $custom-control-indicator-bg;
border: $custom-control-indicator-border-color solid $custom-control-indicator-border-width;
@include box-shadow($custom-control-indicator-box-shadow);
}
// Foreground (icon)
&::after {
position: absolute;
top: ($font-size-base * $line-height-base - $custom-control-indicator-size) / 2;
left: -($custom-control-gutter + $custom-control-indicator-size);
display: block;
width: $custom-control-indicator-size;
height: $custom-control-indicator-size;
content: "";
background-repeat: no-repeat;
background-position: center center;
background-size: $custom-control-indicator-bg-size;
}
}
// Checkboxes
//
// Tweak just a few things for checkboxes.
.custom-checkbox {
.custom-control-label::before {
@include border-radius($custom-checkbox-indicator-border-radius);
}
.custom-control-input:checked ~ .custom-control-label {
&::after {
background-image: $custom-checkbox-indicator-icon-checked;
}
}
.custom-control-input:indeterminate ~ .custom-control-label {
&::before {
border-color: $custom-checkbox-indicator-indeterminate-border-color;
@include gradient-bg($custom-checkbox-indicator-indeterminate-bg);
@include box-shadow($custom-checkbox-indicator-indeterminate-box-shadow);
}
&::after {
background-image: $custom-checkbox-indicator-icon-indeterminate;
}
}
.custom-control-input:disabled {
&:checked ~ .custom-control-label::before {
background-color: $custom-control-indicator-checked-disabled-bg;
}
&:indeterminate ~ .custom-control-label::before {
background-color: $custom-control-indicator-checked-disabled-bg;
}
}
}
// Radios
//
// Tweak just a few things for radios.
.custom-radio {
.custom-control-label::before {
border-radius: $custom-radio-indicator-border-radius;
}
.custom-control-input:checked ~ .custom-control-label {
&::after {
background-image: $custom-radio-indicator-icon-checked;
}
}
.custom-control-input:disabled {
&:checked ~ .custom-control-label::before {
background-color: $custom-control-indicator-checked-disabled-bg;
}
}
}
// switches
//
// Tweak a few things for switches
.custom-switch {
padding-left: $custom-switch-width + $custom-control-gutter;
.custom-control-label {
&::before {
left: -($custom-switch-width + $custom-control-gutter);
width: $custom-switch-width;
pointer-events: all;
border-radius: $custom-switch-indicator-border-radius;
}
&::after {
top: calc(#{(($font-size-base * $line-height-base - $custom-control-indicator-size) / 2)} + #{$custom-control-indicator-border-width * 2});
left: calc(#{-($custom-switch-width + $custom-control-gutter)} + #{$custom-control-indicator-border-width * 2});
width: $custom-switch-indicator-size;
height: $custom-switch-indicator-size;
background-color: $custom-control-indicator-border-color;
border-radius: $custom-switch-indicator-border-radius;
@include transition(transform .15s ease-in-out, $custom-forms-transition);
}
}
.custom-control-input:checked ~ .custom-control-label {
&::after {
background-color: $custom-control-indicator-bg;
transform: translateX($custom-switch-width - $custom-control-indicator-size);
}
}
.custom-control-input:disabled {
&:checked ~ .custom-control-label::before {
background-color: $custom-control-indicator-checked-disabled-bg;
}
}
}
// Select
//
// Replaces the browser default select with a custom one, mostly pulled from
// https://primer.github.io/.
//
.custom-select {
display: inline-block;
width: 100%;
height: $custom-select-height;
padding: $custom-select-padding-y ($custom-select-padding-x + $custom-select-indicator-padding) $custom-select-padding-y $custom-select-padding-x;
font-weight: $custom-select-font-weight;
line-height: $custom-select-line-height;
color: $custom-select-color;
vertical-align: middle;
background: $custom-select-background;
background-color: $custom-select-bg;
border: $custom-select-border-width solid $custom-select-border-color;
@if $enable-rounded {
border-radius: $custom-select-border-radius;
} @else {
border-radius: 0;
}
@include box-shadow($custom-select-box-shadow);
appearance: none;
&:focus {
border-color: $custom-select-focus-border-color;
outline: 0;
@if $enable-shadows {
box-shadow: $custom-select-box-shadow, $custom-select-focus-box-shadow;
} @else {
box-shadow: $custom-select-focus-box-shadow;
}
&::-ms-value {
// For visual consistency with other platforms/browsers,
// suppress the default white text on blue background highlight given to
// the selected option text when the (still closed) <select> receives focus
// in IE and (under certain conditions) Edge.
// See https://github.com/twbs/bootstrap/issues/19398.
color: $input-color;
background-color: $input-bg;
}
}
&[multiple],
&[size]:not([size="1"]) {
height: auto;
padding-right: $custom-select-padding-x;
background-image: none;
}
&:disabled {
color: $custom-select-disabled-color;
background-color: $custom-select-disabled-bg;
}
// Hides the default caret in IE11
&::-ms-expand {
opacity: 0;
}
}
.custom-select-sm {
height: $custom-select-height-sm;
padding-top: $custom-select-padding-y-sm;
padding-bottom: $custom-select-padding-y-sm;
padding-left: $custom-select-padding-x-sm;
font-size: $custom-select-font-size-sm;
}
.custom-select-lg {
height: $custom-select-height-lg;
padding-top: $custom-select-padding-y-lg;
padding-bottom: $custom-select-padding-y-lg;
padding-left: $custom-select-padding-x-lg;
font-size: $custom-select-font-size-lg;
}
// File
//
// Custom file input.
.custom-file {
position: relative;
display: inline-block;
width: 100%;
height: $custom-file-height;
margin-bottom: 0;
}
.custom-file-input {
position: relative;
z-index: 2;
width: 100%;
height: $custom-file-height;
margin: 0;
opacity: 0;
&:focus ~ .custom-file-label {
border-color: $custom-file-focus-border-color;
box-shadow: $custom-file-focus-box-shadow;
}
&:disabled ~ .custom-file-label {
background-color: $custom-file-disabled-bg;
}
@each $lang, $value in $custom-file-text {
&:lang(#{$lang}) ~ .custom-file-label::after {
content: $value;
}
}
~ .custom-file-label[data-browse]::after {
content: attr(data-browse);
}
}
.custom-file-label {
position: absolute;
top: 0;
right: 0;
left: 0;
z-index: 1;
height: $custom-file-height;
padding: $custom-file-padding-y $custom-file-padding-x;
font-weight: $custom-file-font-weight;
line-height: $custom-file-line-height;
color: $custom-file-color;
background-color: $custom-file-bg;
border: $custom-file-border-width solid $custom-file-border-color;
@include border-radius($custom-file-border-radius);
@include box-shadow($custom-file-box-shadow);
&::after {
position: absolute;
top: 0;
right: 0;
bottom: 0;
z-index: 3;
display: block;
height: $custom-file-height-inner;
padding: $custom-file-padding-y $custom-file-padding-x;
line-height: $custom-file-line-height;
color: $custom-file-button-color;
content: "Browse";
@include gradient-bg($custom-file-button-bg);
border-left: inherit;
@include border-radius(0 $custom-file-border-radius $custom-file-border-radius 0);
}
}
// Range
//
// Style range inputs the same across browsers. Vendor-specific rules for pseudo
// elements cannot be mixed. As such, there are no shared styles for focus or
// active states on prefixed selectors.
.custom-range {
width: 100%;
height: calc(#{$custom-range-thumb-height} + #{$custom-range-thumb-focus-box-shadow-width * 2});
padding: 0; // Need to reset padding
background-color: transparent;
appearance: none;
&:focus {
outline: none;
// Pseudo-elements must be split across multiple rulesets to have an effect.
// No box-shadow() mixin for focus accessibility.
&::-webkit-slider-thumb { box-shadow: $custom-range-thumb-focus-box-shadow; }
&::-moz-range-thumb { box-shadow: $custom-range-thumb-focus-box-shadow; }
&::-ms-thumb { box-shadow: $custom-range-thumb-focus-box-shadow; }
}
&::-moz-focus-outer {
border: 0;
}
&::-webkit-slider-thumb {
width: $custom-range-thumb-width;
height: $custom-range-thumb-height;
margin-top: ($custom-range-track-height - $custom-range-thumb-height) / 2; // Webkit specific
@include gradient-bg($custom-range-thumb-bg);
border: $custom-range-thumb-border;
@include border-radius($custom-range-thumb-border-radius);
@include box-shadow($custom-range-thumb-box-shadow);
@include transition($custom-forms-transition);
appearance: none;
&:active {
@include gradient-bg($custom-range-thumb-active-bg);
}
}
&::-webkit-slider-runnable-track {
width: $custom-range-track-width;
height: $custom-range-track-height;
color: transparent; // Why?
cursor: $custom-range-track-cursor;
background-color: $custom-range-track-bg;
border-color: transparent;
@include border-radius($custom-range-track-border-radius);
@include box-shadow($custom-range-track-box-shadow);
}
&::-moz-range-thumb {
width: $custom-range-thumb-width;
height: $custom-range-thumb-height;
@include gradient-bg($custom-range-thumb-bg);
border: $custom-range-thumb-border;
@include border-radius($custom-range-thumb-border-radius);
@include box-shadow($custom-range-thumb-box-shadow);
@include transition($custom-forms-transition);
appearance: none;
&:active {
@include gradient-bg($custom-range-thumb-active-bg);
}
}
&::-moz-range-track {
width: $custom-range-track-width;
height: $custom-range-track-height;
color: transparent;
cursor: $custom-range-track-cursor;
background-color: $custom-range-track-bg;
border-color: transparent; // Firefox specific?
@include border-radius($custom-range-track-border-radius);
@include box-shadow($custom-range-track-box-shadow);
}
&::-ms-thumb {
width: $custom-range-thumb-width;
height: $custom-range-thumb-height;
margin-top: 0; // Edge specific
margin-right: $custom-range-thumb-focus-box-shadow-width; // Workaround that overflowed box-shadow is hidden.
margin-left: $custom-range-thumb-focus-box-shadow-width; // Workaround that overflowed box-shadow is hidden.
@include gradient-bg($custom-range-thumb-bg);
border: $custom-range-thumb-border;
@include border-radius($custom-range-thumb-border-radius);
@include box-shadow($custom-range-thumb-box-shadow);
@include transition($custom-forms-transition);
appearance: none;
&:active {
@include gradient-bg($custom-range-thumb-active-bg);
}
}
&::-ms-track {
width: $custom-range-track-width;
height: $custom-range-track-height;
color: transparent;
cursor: $custom-range-track-cursor;
background-color: transparent;
border-color: transparent;
border-width: $custom-range-thumb-height / 2;
@include box-shadow($custom-range-track-box-shadow);
}
&::-ms-fill-lower {
background-color: $custom-range-track-bg;
@include border-radius($custom-range-track-border-radius);
}
&::-ms-fill-upper {
margin-right: 15px; // arbitrary?
background-color: $custom-range-track-bg;
@include border-radius($custom-range-track-border-radius);
}
&:disabled {
&::-webkit-slider-thumb {
background-color: $custom-range-thumb-disabled-bg;
}
&::-webkit-slider-runnable-track {
cursor: default;
}
&::-moz-range-thumb {
background-color: $custom-range-thumb-disabled-bg;
}
&::-moz-range-track {
cursor: default;
}
&::-ms-thumb {
background-color: $custom-range-thumb-disabled-bg;
}
}
}
.custom-control-label::before,
.custom-file-label,
.custom-select {
@include transition($custom-forms-transition);
}
+191
View File
@@ -0,0 +1,191 @@
// The dropdown wrapper (`<div>`)
.dropup,
.dropright,
.dropdown,
.dropleft {
position: relative;
}
.dropdown-toggle {
// Generate the caret automatically
@include caret;
}
// The dropdown menu
.dropdown-menu {
position: absolute;
top: 100%;
left: 0;
z-index: $zindex-dropdown;
display: none; // none by default, but block on "open" of the menu
float: left;
min-width: $dropdown-min-width;
padding: $dropdown-padding-y 0;
margin: $dropdown-spacer 0 0; // override default ul
font-size: $font-size-base; // Redeclare because nesting can cause inheritance issues
color: $body-color;
text-align: left; // Ensures proper alignment if parent has it changed (e.g., modal footer)
list-style: none;
background-color: $dropdown-bg;
background-clip: padding-box;
border: $dropdown-border-width solid $dropdown-border-color;
@include border-radius($dropdown-border-radius);
@include box-shadow($dropdown-box-shadow);
}
@each $breakpoint in map-keys($grid-breakpoints) {
@include media-breakpoint-up($breakpoint) {
$infix: breakpoint-infix($breakpoint, $grid-breakpoints);
.dropdown-menu#{$infix}-right {
right: 0;
left: auto;
}
}
}
@each $breakpoint in map-keys($grid-breakpoints) {
@include media-breakpoint-up($breakpoint) {
$infix: breakpoint-infix($breakpoint, $grid-breakpoints);
.dropdown-menu#{$infix}-left {
right: auto;
left: 0;
}
}
}
// Allow for dropdowns to go bottom up (aka, dropup-menu)
// Just add .dropup after the standard .dropdown class and you're set.
.dropup {
.dropdown-menu {
top: auto;
bottom: 100%;
margin-top: 0;
margin-bottom: $dropdown-spacer;
}
.dropdown-toggle {
@include caret(up);
}
}
.dropright {
.dropdown-menu {
top: 0;
right: auto;
left: 100%;
margin-top: 0;
margin-left: $dropdown-spacer;
}
.dropdown-toggle {
@include caret(right);
&::after {
vertical-align: 0;
}
}
}
.dropleft {
.dropdown-menu {
top: 0;
right: 100%;
left: auto;
margin-top: 0;
margin-right: $dropdown-spacer;
}
.dropdown-toggle {
@include caret(left);
&::before {
vertical-align: 0;
}
}
}
// When enabled Popper.js, reset basic dropdown position
// stylelint-disable-next-line no-duplicate-selectors
.dropdown-menu {
&[x-placement^="top"],
&[x-placement^="right"],
&[x-placement^="bottom"],
&[x-placement^="left"] {
right: auto;
bottom: auto;
}
}
// Dividers (basically an `<hr>`) within the dropdown
.dropdown-divider {
@include nav-divider($dropdown-divider-bg);
}
// Links, buttons, and more within the dropdown menu
//
// `<button>`-specific styles are denoted with `// For <button>s`
.dropdown-item {
display: block;
width: 100%; // For `<button>`s
padding: $dropdown-item-padding-y $dropdown-item-padding-x;
clear: both;
font-weight: $font-weight-normal;
color: $dropdown-link-color;
text-align: inherit; // For `<button>`s
white-space: nowrap; // prevent links from randomly breaking onto new lines
background-color: transparent; // For `<button>`s
border: 0; // For `<button>`s
&:first-child {
@include border-top-radius($dropdown-inner-border-radius);
}
&:last-child {
@include border-bottom-radius($dropdown-inner-border-radius);
}
@include hover-focus {
color: $dropdown-link-hover-color;
text-decoration: none;
@include gradient-bg($dropdown-link-hover-bg);
}
&.active,
&:active {
color: $dropdown-link-active-color;
text-decoration: none;
@include gradient-bg($dropdown-link-active-bg);
}
&.disabled,
&:disabled {
color: $dropdown-link-disabled-color;
pointer-events: none;
background-color: transparent;
// Remove CSS gradients if they're enabled
@if $enable-gradients {
background-image: none;
}
}
}
.dropdown-menu.show {
display: block;
}
// Dropdown section headers
.dropdown-header {
display: block;
padding: $dropdown-padding-y $dropdown-item-padding-x;
margin-bottom: 0; // for use with heading elements
font-size: $font-size-sm;
color: $dropdown-header-color;
white-space: nowrap; // as with > li > a
}
// Dropdown text
.dropdown-item-text {
display: block;
padding: $dropdown-item-padding-y $dropdown-item-padding-x;
color: $dropdown-link-color;
}
+334
View File
@@ -0,0 +1,334 @@
// stylelint-disable selector-no-qualifying-type
//
// Textual form controls
//
.form-control {
display: block;
width: 100%;
height: $input-height;
padding: $input-padding-y $input-padding-x;
font-size: $input-font-size;
font-weight: $input-font-weight;
line-height: $input-line-height;
color: $input-color;
background-color: $input-bg;
background-clip: padding-box;
border: $input-border-width solid $input-border-color;
// Note: This has no effect on <select>s in some browsers, due to the limited stylability of `<select>`s in CSS.
@if $enable-rounded {
// Manually use the if/else instead of the mixin to account for iOS override
border-radius: $input-border-radius;
} @else {
// Otherwise undo the iOS default
border-radius: 0;
}
@include box-shadow($input-box-shadow);
@include transition($input-transition);
// Unstyle the caret on `<select>`s in IE10+.
&::-ms-expand {
background-color: transparent;
border: 0;
}
// Customize the `:focus` state to imitate native WebKit styles.
@include form-control-focus();
// Placeholder
&::placeholder {
color: $input-placeholder-color;
// Override Firefox's unusual default opacity; see https://github.com/twbs/bootstrap/pull/11526.
opacity: 1;
}
// Disabled and read-only inputs
//
// HTML5 says that controls under a fieldset > legend:first-child won't be
// disabled if the fieldset is disabled. Due to implementation difficulty, we
// don't honor that edge case; we style them as disabled anyway.
&:disabled,
&[readonly] {
background-color: $input-disabled-bg;
// iOS fix for unreadable disabled content; see https://github.com/twbs/bootstrap/issues/11655.
opacity: 1;
}
}
select.form-control {
&:focus::-ms-value {
// Suppress the nested default white text on blue background highlight given to
// the selected option text when the (still closed) <select> receives focus
// in IE and (under certain conditions) Edge, as it looks bad and cannot be made to
// match the appearance of the native widget.
// See https://github.com/twbs/bootstrap/issues/19398.
color: $input-color;
background-color: $input-bg;
}
}
// Make file inputs better match text inputs by forcing them to new lines.
.form-control-file,
.form-control-range {
display: block;
width: 100%;
}
//
// Labels
//
// For use with horizontal and inline forms, when you need the label (or legend)
// text to align with the form controls.
.col-form-label {
padding-top: calc(#{$input-padding-y} + #{$input-border-width});
padding-bottom: calc(#{$input-padding-y} + #{$input-border-width});
margin-bottom: 0; // Override the `<label>/<legend>` default
font-size: inherit; // Override the `<legend>` default
line-height: $input-line-height;
}
.col-form-label-lg {
padding-top: calc(#{$input-padding-y-lg} + #{$input-border-width});
padding-bottom: calc(#{$input-padding-y-lg} + #{$input-border-width});
font-size: $input-font-size-lg;
line-height: $input-line-height-lg;
}
.col-form-label-sm {
padding-top: calc(#{$input-padding-y-sm} + #{$input-border-width});
padding-bottom: calc(#{$input-padding-y-sm} + #{$input-border-width});
font-size: $input-font-size-sm;
line-height: $input-line-height-sm;
}
// Readonly controls as plain text
//
// Apply class to a readonly input to make it appear like regular plain
// text (without any border, background color, focus indicator)
.form-control-plaintext {
display: block;
width: 100%;
padding-top: $input-padding-y;
padding-bottom: $input-padding-y;
margin-bottom: 0; // match inputs if this class comes on inputs with default margins
line-height: $input-line-height;
color: $input-plaintext-color;
background-color: transparent;
border: solid transparent;
border-width: $input-border-width 0;
&.form-control-sm,
&.form-control-lg {
padding-right: 0;
padding-left: 0;
}
}
// Form control sizing
//
// Build on `.form-control` with modifier classes to decrease or increase the
// height and font-size of form controls.
//
// Repeated in `_input_group.scss` to avoid Sass extend issues.
.form-control-sm {
height: $input-height-sm;
padding: $input-padding-y-sm $input-padding-x-sm;
font-size: $input-font-size-sm;
line-height: $input-line-height-sm;
@include border-radius($input-border-radius-sm);
}
.form-control-lg {
height: $input-height-lg;
padding: $input-padding-y-lg $input-padding-x-lg;
font-size: $input-font-size-lg;
line-height: $input-line-height-lg;
@include border-radius($input-border-radius-lg);
}
// stylelint-disable-next-line no-duplicate-selectors
select.form-control {
&[size],
&[multiple] {
height: auto;
}
}
// stylelint-disable-next-line no-duplicate-selectors
textarea.form-control {
height: auto;
}
// Form groups
//
// Designed to help with the organization and spacing of vertical forms. For
// horizontal forms, use the predefined grid classes.
.form-group {
margin-bottom: $form-group-margin-bottom;
}
.form-text {
display: block;
margin-top: $form-text-margin-top;
}
// Form grid
//
// Special replacement for our grid system's `.row` for tighter form layouts.
.form-row {
display: flex;
flex-wrap: wrap;
margin-right: -$form-grid-gutter-width / 2;
margin-left: -$form-grid-gutter-width / 2;
> .col,
> [class*="col-"] {
padding-right: $form-grid-gutter-width / 2;
padding-left: $form-grid-gutter-width / 2;
}
}
// Checkboxes and radios
//
// Indent the labels to position radios/checkboxes as hanging controls.
.form-check {
position: relative;
display: block;
padding-left: $form-check-input-gutter;
}
.form-check-input {
position: absolute;
margin-top: $form-check-input-margin-y;
margin-left: -$form-check-input-gutter;
&:disabled ~ .form-check-label {
color: $text-muted;
}
}
.form-check-label {
margin-bottom: 0; // Override default `<label>` bottom margin
}
.form-check-inline {
display: inline-flex;
align-items: center;
padding-left: 0; // Override base .form-check
margin-right: $form-check-inline-margin-x;
// Undo .form-check-input defaults and add some `margin-right`.
.form-check-input {
position: static;
margin-top: 0;
margin-right: $form-check-inline-input-margin-x;
margin-left: 0;
}
}
// Form validation
//
// Provide feedback to users when form field values are valid or invalid. Works
// primarily for client-side validation via scoped `:invalid` and `:valid`
// pseudo-classes but also includes `.is-invalid` and `.is-valid` classes for
// server side validation.
@include form-validation-state("valid", $form-feedback-valid-color);
@include form-validation-state("invalid", $form-feedback-invalid-color);
// Inline forms
//
// Make forms appear inline(-block) by adding the `.form-inline` class. Inline
// forms begin stacked on extra small (mobile) devices and then go inline when
// viewports reach <768px.
//
// Requires wrapping inputs and labels with `.form-group` for proper display of
// default HTML form controls and our custom form controls (e.g., input groups).
.form-inline {
display: flex;
flex-flow: row wrap;
align-items: center; // Prevent shorter elements from growing to same height as others (e.g., small buttons growing to normal sized button height)
// Because we use flex, the initial sizing of checkboxes is collapsed and
// doesn't occupy the full-width (which is what we want for xs grid tier),
// so we force that here.
.form-check {
width: 100%;
}
// Kick in the inline
@include media-breakpoint-up(sm) {
label {
display: flex;
align-items: center;
justify-content: center;
margin-bottom: 0;
}
// Inline-block all the things for "inline"
.form-group {
display: flex;
flex: 0 0 auto;
flex-flow: row wrap;
align-items: center;
margin-bottom: 0;
}
// Allow folks to *not* use `.form-group`
.form-control {
display: inline-block;
width: auto; // Prevent labels from stacking above inputs in `.form-group`
vertical-align: middle;
}
// Make static controls behave like regular ones
.form-control-plaintext {
display: inline-block;
}
.input-group,
.custom-select {
width: auto;
}
// Remove default margin on radios/checkboxes that were used for stacking, and
// then undo the floating of radios and checkboxes to match.
.form-check {
display: flex;
align-items: center;
justify-content: center;
width: auto;
padding-left: 0;
}
.form-check-input {
position: relative;
margin-top: 0;
margin-right: $form-check-input-margin-x;
margin-left: 0;
}
.custom-control {
align-items: center;
justify-content: center;
}
.custom-control-label {
margin-bottom: 0;
}
}
}
@@ -0,0 +1,86 @@
// Bootstrap functions
//
// Utility mixins and functions for evaluating source code across our variables, maps, and mixins.
// Ascending
// Used to evaluate Sass maps like our grid breakpoints.
@mixin _assert-ascending($map, $map-name) {
$prev-key: null;
$prev-num: null;
@each $key, $num in $map {
@if $prev-num == null or unit($num) == "%" {
// Do nothing
} @else if not comparable($prev-num, $num) {
@warn "Potentially invalid value for #{$map-name}: This map must be in ascending order, but key '#{$key}' has value #{$num} whose unit makes it incomparable to #{$prev-num}, the value of the previous key '#{$prev-key}' !";
} @else if $prev-num >= $num {
@warn "Invalid value for #{$map-name}: This map must be in ascending order, but key '#{$key}' has value #{$num} which isn't greater than #{$prev-num}, the value of the previous key '#{$prev-key}' !";
}
$prev-key: $key;
$prev-num: $num;
}
}
// Starts at zero
// Another grid mixin that ensures the min-width of the lowest breakpoint starts at 0.
@mixin _assert-starts-at-zero($map) {
$values: map-values($map);
$first-value: nth($values, 1);
@if $first-value != 0 {
@warn "First breakpoint in `$grid-breakpoints` must start at 0, but starts at #{$first-value}.";
}
}
// Replace `$search` with `$replace` in `$string`
// Used on our SVG icon backgrounds for custom forms.
//
// @author Hugo Giraudel
// @param {String} $string - Initial string
// @param {String} $search - Substring to replace
// @param {String} $replace ('') - New value
// @return {String} - Updated string
@function str-replace($string, $search, $replace: "") {
$index: str-index($string, $search);
@if $index {
@return str-slice($string, 1, $index - 1) + $replace + str-replace(str-slice($string, $index + str-length($search)), $search, $replace);
}
@return $string;
}
// Color contrast
@function color-yiq($color, $dark: $yiq-text-dark, $light: $yiq-text-light) {
$r: red($color);
$g: green($color);
$b: blue($color);
$yiq: (($r * 299) + ($g * 587) + ($b * 114)) / 1000;
@if ($yiq >= $yiq-contrasted-threshold) {
@return $dark;
} @else {
@return $light;
}
}
// Retrieve color Sass maps
@function color($key: "blue") {
@return map-get($colors, $key);
}
@function theme-color($key: "primary") {
@return map-get($theme-colors, $key);
}
@function gray($key: "100") {
@return map-get($grays, $key);
}
// Request a theme color level
@function theme-color-level($color-name: "primary", $level: 0) {
$color: theme-color($color-name);
$color-base: if($level > 0, $black, $white);
$level: abs($level);
@return mix($color-base, $color, $level * $theme-color-interval);
}
+52
View File
@@ -0,0 +1,52 @@
// Container widths
//
// Set the container width, and override it for fixed navbars in media queries.
@if $enable-grid-classes {
.container {
@include make-container();
@include make-container-max-widths();
}
}
// Fluid container
//
// Utilizes the mixin meant for fixed width containers, but with 100% width for
// fluid, full width layouts.
@if $enable-grid-classes {
.container-fluid {
@include make-container();
}
}
// Row
//
// Rows contain and clear the floats of your columns.
@if $enable-grid-classes {
.row {
@include make-row();
}
// Remove the negative margin from default .row, then the horizontal padding
// from all immediate children columns (to prevent runaway style inheritance).
.no-gutters {
margin-right: 0;
margin-left: 0;
> .col,
> [class*="col-"] {
padding-right: 0;
padding-left: 0;
}
}
}
// Columns
//
// Common styles for small and large grid columns
@if $enable-grid-classes {
@include make-grid-columns();
}
+42
View File
@@ -0,0 +1,42 @@
// Responsive images (ensure images don't scale beyond their parents)
//
// This is purposefully opt-in via an explicit class rather than being the default for all `<img>`s.
// We previously tried the "images are responsive by default" approach in Bootstrap v2,
// and abandoned it in Bootstrap v3 because it breaks lots of third-party widgets (including Google Maps)
// which weren't expecting the images within themselves to be involuntarily resized.
// See also https://github.com/twbs/bootstrap/issues/18178
.img-fluid {
@include img-fluid;
}
// Image thumbnails
.img-thumbnail {
padding: $thumbnail-padding;
background-color: $thumbnail-bg;
border: $thumbnail-border-width solid $thumbnail-border-color;
@include border-radius($thumbnail-border-radius);
@include box-shadow($thumbnail-box-shadow);
// Keep them at most 100% wide
@include img-fluid;
}
//
// Figures
//
.figure {
// Ensures the caption's text aligns with the image.
display: inline-block;
}
.figure-img {
margin-bottom: $spacer / 2;
line-height: 1;
}
.figure-caption {
font-size: $figure-caption-font-size;
color: $figure-caption-color;
}
@@ -0,0 +1,193 @@
// stylelint-disable selector-no-qualifying-type
//
// Base styles
//
.input-group {
position: relative;
display: flex;
flex-wrap: wrap; // For form validation feedback
align-items: stretch;
width: 100%;
> .form-control,
> .form-control-plaintext,
> .custom-select,
> .custom-file {
position: relative; // For focus state's z-index
flex: 1 1 auto;
// Add width 1% and flex-basis auto to ensure that button will not wrap out
// the column. Applies to IE Edge+ and Firefox. Chrome does not require this.
width: 1%;
margin-bottom: 0;
+ .form-control,
+ .custom-select,
+ .custom-file {
margin-left: -$input-border-width;
}
}
// Bring the "active" form control to the top of surrounding elements
> .form-control:focus,
> .custom-select:focus,
> .custom-file .custom-file-input:focus ~ .custom-file-label {
z-index: 3;
}
// Bring the custom file input above the label
> .custom-file .custom-file-input:focus {
z-index: 4;
}
> .form-control,
> .custom-select {
&:not(:last-child) { @include border-right-radius(0); }
&:not(:first-child) { @include border-left-radius(0); }
}
// Custom file inputs have more complex markup, thus requiring different
// border-radius overrides.
> .custom-file {
display: flex;
align-items: center;
&:not(:last-child) .custom-file-label,
&:not(:last-child) .custom-file-label::after { @include border-right-radius(0); }
&:not(:first-child) .custom-file-label { @include border-left-radius(0); }
}
}
// Prepend and append
//
// While it requires one extra layer of HTML for each, dedicated prepend and
// append elements allow us to 1) be less clever, 2) simplify our selectors, and
// 3) support HTML5 form validation.
.input-group-prepend,
.input-group-append {
display: flex;
// Ensure buttons are always above inputs for more visually pleasing borders.
// This isn't needed for `.input-group-text` since it shares the same border-color
// as our inputs.
.btn {
position: relative;
z-index: 2;
&:focus {
z-index: 3;
}
}
.btn + .btn,
.btn + .input-group-text,
.input-group-text + .input-group-text,
.input-group-text + .btn {
margin-left: -$input-border-width;
}
}
.input-group-prepend { margin-right: -$input-border-width; }
.input-group-append { margin-left: -$input-border-width; }
// Textual addons
//
// Serves as a catch-all element for any text or radio/checkbox input you wish
// to prepend or append to an input.
.input-group-text {
display: flex;
align-items: center;
padding: $input-padding-y $input-padding-x;
margin-bottom: 0; // Allow use of <label> elements by overriding our default margin-bottom
font-size: $font-size-base; // Match inputs
font-weight: $font-weight-normal;
line-height: $input-line-height;
color: $input-group-addon-color;
text-align: center;
white-space: nowrap;
background-color: $input-group-addon-bg;
border: $input-border-width solid $input-group-addon-border-color;
@include border-radius($input-border-radius);
// Nuke default margins from checkboxes and radios to vertically center within.
input[type="radio"],
input[type="checkbox"] {
margin-top: 0;
}
}
// Sizing
//
// Remix the default form control sizing classes into new ones for easier
// manipulation.
.input-group-lg > .form-control:not(textarea),
.input-group-lg > .custom-select {
height: $input-height-lg;
}
.input-group-lg > .form-control,
.input-group-lg > .custom-select,
.input-group-lg > .input-group-prepend > .input-group-text,
.input-group-lg > .input-group-append > .input-group-text,
.input-group-lg > .input-group-prepend > .btn,
.input-group-lg > .input-group-append > .btn {
padding: $input-padding-y-lg $input-padding-x-lg;
font-size: $input-font-size-lg;
line-height: $input-line-height-lg;
@include border-radius($input-border-radius-lg);
}
.input-group-sm > .form-control:not(textarea),
.input-group-sm > .custom-select {
height: $input-height-sm;
}
.input-group-sm > .form-control,
.input-group-sm > .custom-select,
.input-group-sm > .input-group-prepend > .input-group-text,
.input-group-sm > .input-group-append > .input-group-text,
.input-group-sm > .input-group-prepend > .btn,
.input-group-sm > .input-group-append > .btn {
padding: $input-padding-y-sm $input-padding-x-sm;
font-size: $input-font-size-sm;
line-height: $input-line-height-sm;
@include border-radius($input-border-radius-sm);
}
.input-group-lg > .custom-select,
.input-group-sm > .custom-select {
padding-right: $custom-select-padding-x + $custom-select-indicator-padding;
}
// Prepend and append rounded corners
//
// These rulesets must come after the sizing ones to properly override sm and lg
// border-radius values when extending. They're more specific than we'd like
// with the `.input-group >` part, but without it, we cannot override the sizing.
.input-group > .input-group-prepend > .btn,
.input-group > .input-group-prepend > .input-group-text,
.input-group > .input-group-append:not(:last-child) > .btn,
.input-group > .input-group-append:not(:last-child) > .input-group-text,
.input-group > .input-group-append:last-child > .btn:not(:last-child):not(.dropdown-toggle),
.input-group > .input-group-append:last-child > .input-group-text:not(:last-child) {
@include border-right-radius(0);
}
.input-group > .input-group-append > .btn,
.input-group > .input-group-append > .input-group-text,
.input-group > .input-group-prepend:not(:first-child) > .btn,
.input-group > .input-group-prepend:not(:first-child) > .input-group-text,
.input-group > .input-group-prepend:first-child > .btn:not(:first-child),
.input-group > .input-group-prepend:first-child > .input-group-text:not(:first-child) {
@include border-left-radius(0);
}
@@ -0,0 +1,16 @@
.jumbotron {
padding: $jumbotron-padding ($jumbotron-padding / 2);
margin-bottom: $jumbotron-padding;
background-color: $jumbotron-bg;
@include border-radius($border-radius-lg);
@include media-breakpoint-up(sm) {
padding: ($jumbotron-padding * 2) $jumbotron-padding;
}
}
.jumbotron-fluid {
padding-right: 0;
padding-left: 0;
@include border-radius(0);
}
@@ -0,0 +1,121 @@
// Base class
//
// Easily usable on <ul>, <ol>, or <div>.
.list-group {
display: flex;
flex-direction: column;
// No need to set list-style: none; since .list-group-item is block level
padding-left: 0; // reset padding because ul and ol
margin-bottom: 0;
}
// Interactive list items
//
// Use anchor or button elements instead of `li`s or `div`s to create interactive
// list items. Includes an extra `.active` modifier class for selected items.
.list-group-item-action {
width: 100%; // For `<button>`s (anchors become 100% by default though)
color: $list-group-action-color;
text-align: inherit; // For `<button>`s (anchors inherit)
// Hover state
@include hover-focus {
color: $list-group-action-hover-color;
text-decoration: none;
background-color: $list-group-hover-bg;
}
&:active {
color: $list-group-action-active-color;
background-color: $list-group-action-active-bg;
}
}
// Individual list items
//
// Use on `li`s or `div`s within the `.list-group` parent.
.list-group-item {
position: relative;
display: block;
padding: $list-group-item-padding-y $list-group-item-padding-x;
// Place the border on the list items and negative margin up for better styling
margin-bottom: -$list-group-border-width;
background-color: $list-group-bg;
border: $list-group-border-width solid $list-group-border-color;
&:first-child {
@include border-top-radius($list-group-border-radius);
}
&:last-child {
margin-bottom: 0;
@include border-bottom-radius($list-group-border-radius);
}
@include hover-focus {
z-index: 1; // Place hover/active items above their siblings for proper border styling
text-decoration: none;
}
&.disabled,
&:disabled {
color: $list-group-disabled-color;
pointer-events: none;
background-color: $list-group-disabled-bg;
}
// Include both here for `<a>`s and `<button>`s
&.active {
z-index: 2; // Place active items above their siblings for proper border styling
color: $list-group-active-color;
background-color: $list-group-active-bg;
border-color: $list-group-active-border-color;
}
}
// Flush list items
//
// Remove borders and border-radius to keep list group items edge-to-edge. Most
// useful within other components (e.g., cards).
.list-group-flush {
.list-group-item {
border-right: 0;
border-left: 0;
@include border-radius(0);
&:last-child {
margin-bottom: -$list-group-border-width;
}
}
&:first-child {
.list-group-item:first-child {
border-top: 0;
}
}
&:last-child {
.list-group-item:last-child {
margin-bottom: 0;
border-bottom: 0;
}
}
}
// Contextual variants
//
// Add modifier classes to change text and background color on individual items.
// Organizationally, this must come after the `:hover` states.
@each $color, $value in $theme-colors {
@include list-group-item-variant($color, theme-color-level($color, -9), theme-color-level($color, 6));
}
+8
View File
@@ -0,0 +1,8 @@
.media {
display: flex;
align-items: flex-start;
}
.media-body {
flex: 1;
}
+41
View File
@@ -0,0 +1,41 @@
// Toggles
//
// Used in conjunction with global variables to enable certain theme features.
// Utilities
@import "mixins/breakpoints";
@import "mixins/hover";
@import "mixins/image";
@import "mixins/badge";
@import "mixins/resize";
@import "mixins/screen-reader";
@import "mixins/size";
@import "mixins/reset-text";
@import "mixins/text-emphasis";
@import "mixins/text-hide";
@import "mixins/text-truncate";
@import "mixins/visibility";
// // Components
@import "mixins/alert";
@import "mixins/buttons";
@import "mixins/caret";
@import "mixins/pagination";
@import "mixins/lists";
@import "mixins/list-group";
@import "mixins/nav-divider";
@import "mixins/forms";
@import "mixins/table-row";
// // Skins
@import "mixins/background-variant";
@import "mixins/border-radius";
@import "mixins/box-shadow";
@import "mixins/gradients";
@import "mixins/transition";
// // Layout
@import "mixins/clearfix";
@import "mixins/grid-framework";
@import "mixins/grid";
@import "mixins/float";
+186
View File
@@ -0,0 +1,186 @@
// .modal-open - body class for killing the scroll
// .modal - container to scroll within
// .modal-dialog - positioning shell for the actual modal
// .modal-content - actual modal w/ bg and corners and stuff
.modal-open {
// Kill the scroll on the body
overflow: hidden;
.modal {
overflow-x: hidden;
overflow-y: auto;
}
}
// Container that the modal scrolls within
.modal {
position: fixed;
top: 0;
left: 0;
z-index: $zindex-modal;
display: none;
width: 100%;
height: 100%;
overflow: hidden;
// Prevent Chrome on Windows from adding a focus outline. For details, see
// https://github.com/twbs/bootstrap/pull/10951.
outline: 0;
// We deliberately don't use `-webkit-overflow-scrolling: touch;` due to a
// gnarly iOS Safari bug: https://bugs.webkit.org/show_bug.cgi?id=158342
// See also https://github.com/twbs/bootstrap/issues/17695
}
// Shell div to position the modal with bottom padding
.modal-dialog {
position: relative;
width: auto;
margin: $modal-dialog-margin;
// allow clicks to pass through for custom click handling to close modal
pointer-events: none;
// When fading in the modal, animate it to slide down
.modal.fade & {
@include transition($modal-transition);
transform: $modal-fade-transform;
}
.modal.show & {
transform: $modal-show-transform;
}
}
.modal-dialog-centered {
display: flex;
align-items: center;
min-height: calc(100% - (#{$modal-dialog-margin} * 2));
// Ensure `modal-dialog-centered` extends the full height of the view (IE10/11)
&::before {
display: block; // IE10
height: calc(100vh - (#{$modal-dialog-margin} * 2));
content: "";
}
}
// Actual modal
.modal-content {
position: relative;
display: flex;
flex-direction: column;
width: 100%; // Ensure `.modal-content` extends the full width of the parent `.modal-dialog`
// counteract the pointer-events: none; in the .modal-dialog
pointer-events: auto;
background-color: $modal-content-bg;
background-clip: padding-box;
border: $modal-content-border-width solid $modal-content-border-color;
@include border-radius($modal-content-border-radius);
@include box-shadow($modal-content-box-shadow-xs);
// Remove focus outline from opened modal
outline: 0;
}
// Modal background
.modal-backdrop {
position: fixed;
top: 0;
left: 0;
z-index: $zindex-modal-backdrop;
width: 100vw;
height: 100vh;
background-color: $modal-backdrop-bg;
// Fade for backdrop
&.fade { opacity: 0; }
&.show { opacity: $modal-backdrop-opacity; }
}
// Modal header
// Top section of the modal w/ title and dismiss
.modal-header {
display: flex;
align-items: flex-start; // so the close btn always stays on the upper right corner
justify-content: space-between; // Put modal header elements (title and dismiss) on opposite ends
padding: $modal-header-padding;
border-bottom: $modal-header-border-width solid $modal-header-border-color;
@include border-top-radius($modal-content-border-radius);
.close {
padding: $modal-header-padding;
// auto on the left force icon to the right even when there is no .modal-title
margin: (-$modal-header-padding-y) (-$modal-header-padding-x) (-$modal-header-padding-y) auto;
}
}
// Title text within header
.modal-title {
margin-bottom: 0;
line-height: $modal-title-line-height;
}
// Modal body
// Where all modal content resides (sibling of .modal-header and .modal-footer)
.modal-body {
position: relative;
// Enable `flex-grow: 1` so that the body take up as much space as possible
// when should there be a fixed height on `.modal-dialog`.
flex: 1 1 auto;
padding: $modal-inner-padding;
}
// Footer (for actions)
.modal-footer {
display: flex;
align-items: center; // vertically center
justify-content: flex-end; // Right align buttons with flex property because text-align doesn't work on flex items
padding: $modal-inner-padding;
border-top: $modal-footer-border-width solid $modal-footer-border-color;
@include border-bottom-radius($modal-content-border-radius);
// Easily place margin between footer elements
> :not(:first-child) { margin-left: .25rem; }
> :not(:last-child) { margin-right: .25rem; }
}
// Measure scrollbar width for padding body during modal show/hide
.modal-scrollbar-measure {
position: absolute;
top: -9999px;
width: 50px;
height: 50px;
overflow: scroll;
}
// Scale up the modal
@include media-breakpoint-up(sm) {
// Automatically set modal's width for larger viewports
.modal-dialog {
max-width: $modal-md;
margin: $modal-dialog-margin-y-sm-up auto;
}
.modal-dialog-centered {
min-height: calc(100% - (#{$modal-dialog-margin-y-sm-up} * 2));
&::before {
height: calc(100vh - (#{$modal-dialog-margin-y-sm-up} * 2));
}
}
.modal-content {
@include box-shadow($modal-content-box-shadow-sm-up);
}
.modal-sm { max-width: $modal-sm; }
}
@include media-breakpoint-up(lg) {
.modal-lg,
.modal-xl {
max-width: $modal-lg;
}
}
@include media-breakpoint-up(xl) {
.modal-xl { max-width: $modal-xl; }
}
+121
View File
@@ -0,0 +1,121 @@
// Base class
//
// Kickstart any navigation component with a set of style resets. Works with
// `<nav>`s or `<ul>`s.
.nav {
display: flex;
flex-wrap: wrap;
padding-left: 0;
margin-bottom: 0;
list-style: none;
}
.nav-link {
display: block;
padding: $nav-link-padding-y $nav-link-padding-x;
@include hover-focus {
text-decoration: none;
}
// Disabled state lightens text
&.disabled {
color: $nav-link-disabled-color;
pointer-events: none;
cursor: default;
}
}
//
// Tabs
//
.nav-tabs {
border-bottom: $nav-tabs-border-width solid $nav-tabs-border-color;
.nav-item {
margin-bottom: -$nav-tabs-border-width;
}
.nav-link {
border: $nav-tabs-border-width solid transparent;
@include border-top-radius($nav-tabs-border-radius);
@include hover-focus {
border-color: $nav-tabs-link-hover-border-color;
}
&.disabled {
color: $nav-link-disabled-color;
background-color: transparent;
border-color: transparent;
}
}
.nav-link.active,
.nav-item.show .nav-link {
color: $nav-tabs-link-active-color;
background-color: $nav-tabs-link-active-bg;
border-color: $nav-tabs-link-active-border-color;
}
.dropdown-menu {
// Make dropdown border overlap tab border
margin-top: -$nav-tabs-border-width;
// Remove the top rounded corners here since there is a hard edge above the menu
@include border-top-radius(0);
}
}
//
// Pills
//
.nav-pills {
.nav-link {
@include border-radius($nav-pills-border-radius);
}
.nav-link.active,
.show > .nav-link {
color: $nav-pills-link-active-color;
background-color: $nav-pills-link-active-bg;
}
}
//
// Justified variants
//
.nav-fill {
.nav-item {
flex: 1 1 auto;
text-align: center;
}
}
.nav-justified {
.nav-item {
flex-basis: 0;
flex-grow: 1;
text-align: center;
}
}
// Tabbable tabs
//
// Hide tabbable panes to start, show them when `.active`
.tab-content {
> .tab-pane {
display: none;
outline: none !important;
}
> .active {
display: block;
}
}
+299
View File
@@ -0,0 +1,299 @@
// Contents
//
// Navbar
// Navbar brand
// Navbar nav
// Navbar text
// Navbar divider
// Responsive navbar
// Navbar position
// Navbar themes
// Navbar
//
// Provide a static navbar from which we expand to create full-width, fixed, and
// other navbar variations.
.navbar {
position: relative;
display: flex;
flex-wrap: wrap; // allow us to do the line break for collapsing content
align-items: center;
justify-content: space-between; // space out brand from logo
padding: $navbar-padding-y $navbar-padding-x;
// Because flex properties aren't inherited, we need to redeclare these first
// few properties so that content nested within behave properly.
> .container,
> .container-fluid {
display: flex;
flex-wrap: wrap;
align-items: center;
justify-content: space-between;
}
}
// Navbar brand
//
// Used for brand, project, or site names.
.navbar-brand {
display: inline-block;
padding-top: $navbar-brand-padding-y;
padding-bottom: $navbar-brand-padding-y;
margin-right: $navbar-padding-x;
font-size: $navbar-brand-font-size;
line-height: inherit;
white-space: nowrap;
@include hover-focus {
text-decoration: none;
}
}
// Navbar nav
//
// Custom navbar navigation (doesn't require `.nav`, but does make use of `.nav-link`).
.navbar-nav {
display: flex;
flex-direction: column; // cannot use `inherit` to get the `.navbar`s value
padding-left: 0;
margin-bottom: 0;
list-style: none;
.nav-link {
padding-right: 0;
padding-left: 0;
}
.dropdown-menu {
position: static;
float: none;
}
}
// Navbar text
//
//
.navbar-text {
display: inline-block;
padding-top: $nav-link-padding-y;
padding-bottom: $nav-link-padding-y;
}
// Responsive navbar
//
// Custom styles for responsive collapsing and toggling of navbar contents.
// Powered by the collapse Bootstrap JavaScript plugin.
// When collapsed, prevent the toggleable navbar contents from appearing in
// the default flexbox row orientation. Requires the use of `flex-wrap: wrap`
// on the `.navbar` parent.
.navbar-collapse {
flex-basis: 100%;
flex-grow: 1;
// For always expanded or extra full navbars, ensure content aligns itself
// properly vertically. Can be easily overridden with flex utilities.
align-items: center;
}
// Button for toggling the navbar when in its collapsed state
.navbar-toggler {
padding: $navbar-toggler-padding-y $navbar-toggler-padding-x;
font-size: $navbar-toggler-font-size;
line-height: 1;
background-color: transparent; // remove default button style
border: $border-width solid transparent; // remove default button style
@include border-radius($navbar-toggler-border-radius);
@include hover-focus {
text-decoration: none;
}
// Opinionated: add "hand" cursor to non-disabled .navbar-toggler elements
&:not(:disabled):not(.disabled) {
cursor: pointer;
}
}
// Keep as a separate element so folks can easily override it with another icon
// or image file as needed.
.navbar-toggler-icon {
display: inline-block;
width: 1.5em;
height: 1.5em;
vertical-align: middle;
content: "";
background: no-repeat center center;
background-size: 100% 100%;
}
// Generate series of `.navbar-expand-*` responsive classes for configuring
// where your navbar collapses.
.navbar-expand {
@each $breakpoint in map-keys($grid-breakpoints) {
$next: breakpoint-next($breakpoint, $grid-breakpoints);
$infix: breakpoint-infix($next, $grid-breakpoints);
&#{$infix} {
@include media-breakpoint-down($breakpoint) {
> .container,
> .container-fluid {
padding-right: 0;
padding-left: 0;
}
}
@include media-breakpoint-up($next) {
flex-flow: row nowrap;
justify-content: flex-start;
.navbar-nav {
flex-direction: row;
.dropdown-menu {
position: absolute;
}
.nav-link {
padding-right: $navbar-nav-link-padding-x;
padding-left: $navbar-nav-link-padding-x;
}
}
// For nesting containers, have to redeclare for alignment purposes
> .container,
> .container-fluid {
flex-wrap: nowrap;
}
.navbar-collapse {
display: flex !important; // stylelint-disable-line declaration-no-important
// Changes flex-bases to auto because of an IE10 bug
flex-basis: auto;
}
.navbar-toggler {
display: none;
}
}
}
}
}
// Navbar themes
//
// Styles for switching between navbars with light or dark background.
// Dark links against a light background
.navbar-light {
.navbar-brand {
color: $navbar-light-brand-color;
@include hover-focus {
color: $navbar-light-brand-hover-color;
}
}
.navbar-nav {
.nav-link {
color: $navbar-light-color;
@include hover-focus {
color: $navbar-light-hover-color;
}
&.disabled {
color: $navbar-light-disabled-color;
}
}
.show > .nav-link,
.active > .nav-link,
.nav-link.show,
.nav-link.active {
color: $navbar-light-active-color;
}
}
.navbar-toggler {
color: $navbar-light-color;
border-color: $navbar-light-toggler-border-color;
}
.navbar-toggler-icon {
background-image: $navbar-light-toggler-icon-bg;
}
.navbar-text {
color: $navbar-light-color;
a {
color: $navbar-light-active-color;
@include hover-focus {
color: $navbar-light-active-color;
}
}
}
}
// White links against a dark background
.navbar-dark {
.navbar-brand {
color: $navbar-dark-brand-color;
@include hover-focus {
color: $navbar-dark-brand-hover-color;
}
}
.navbar-nav {
.nav-link {
color: $navbar-dark-color;
@include hover-focus {
color: $navbar-dark-hover-color;
}
&.disabled {
color: $navbar-dark-disabled-color;
}
}
.show > .nav-link,
.active > .nav-link,
.nav-link.show,
.nav-link.active {
color: $navbar-dark-active-color;
}
}
.navbar-toggler {
color: $navbar-dark-color;
border-color: $navbar-dark-toggler-border-color;
}
.navbar-toggler-icon {
background-image: $navbar-dark-toggler-icon-bg;
}
.navbar-text {
color: $navbar-dark-color;
a {
color: $navbar-dark-active-color;
@include hover-focus {
color: $navbar-dark-active-color;
}
}
}
}
@@ -0,0 +1,78 @@
.pagination {
display: flex;
@include list-unstyled();
@include border-radius();
}
.page-link {
position: relative;
display: block;
padding: $pagination-padding-y $pagination-padding-x;
margin-left: -$pagination-border-width;
line-height: $pagination-line-height;
color: $pagination-color;
background-color: $pagination-bg;
border: $pagination-border-width solid $pagination-border-color;
&:hover {
z-index: 2;
color: $pagination-hover-color;
text-decoration: none;
background-color: $pagination-hover-bg;
border-color: $pagination-hover-border-color;
}
&:focus {
z-index: 2;
outline: $pagination-focus-outline;
box-shadow: $pagination-focus-box-shadow;
}
// Opinionated: add "hand" cursor to non-disabled .page-link elements
&:not(:disabled):not(.disabled) {
cursor: pointer;
}
}
.page-item {
&:first-child {
.page-link {
margin-left: 0;
@include border-left-radius($border-radius);
}
}
&:last-child {
.page-link {
@include border-right-radius($border-radius);
}
}
&.active .page-link {
z-index: 1;
color: $pagination-active-color;
background-color: $pagination-active-bg;
border-color: $pagination-active-border-color;
}
&.disabled .page-link {
color: $pagination-disabled-color;
pointer-events: none;
// Opinionated: remove the "hand" cursor set previously for .page-link
cursor: auto;
background-color: $pagination-disabled-bg;
border-color: $pagination-disabled-border-color;
}
}
//
// Sizing
//
.pagination-lg {
@include pagination-size($pagination-padding-y-lg, $pagination-padding-x-lg, $font-size-lg, $line-height-lg, $border-radius-lg);
}
.pagination-sm {
@include pagination-size($pagination-padding-y-sm, $pagination-padding-x-sm, $font-size-sm, $line-height-sm, $border-radius-sm);
}
+183
View File
@@ -0,0 +1,183 @@
.popover {
position: absolute;
top: 0;
left: 0;
z-index: $zindex-popover;
display: block;
max-width: $popover-max-width;
// Our parent element can be arbitrary since tooltips are by default inserted as a sibling of their target element.
// So reset our font and text properties to avoid inheriting weird values.
@include reset-text();
font-size: $popover-font-size;
// Allow breaking very long words so they don't overflow the popover's bounds
word-wrap: break-word;
background-color: $popover-bg;
background-clip: padding-box;
border: $popover-border-width solid $popover-border-color;
@include border-radius($popover-border-radius);
@include box-shadow($popover-box-shadow);
.arrow {
position: absolute;
display: block;
width: $popover-arrow-width;
height: $popover-arrow-height;
margin: 0 $border-radius-lg;
&::before,
&::after {
position: absolute;
display: block;
content: "";
border-color: transparent;
border-style: solid;
}
}
}
.bs-popover-top {
margin-bottom: $popover-arrow-height;
.arrow {
bottom: calc((#{$popover-arrow-height} + #{$popover-border-width}) * -1);
}
.arrow::before,
.arrow::after {
border-width: $popover-arrow-height ($popover-arrow-width / 2) 0;
}
.arrow::before {
bottom: 0;
border-top-color: $popover-arrow-outer-color;
}
.arrow::after {
bottom: $popover-border-width;
border-top-color: $popover-arrow-color;
}
}
.bs-popover-right {
margin-left: $popover-arrow-height;
.arrow {
left: calc((#{$popover-arrow-height} + #{$popover-border-width}) * -1);
width: $popover-arrow-height;
height: $popover-arrow-width;
margin: $border-radius-lg 0; // make sure the arrow does not touch the popover's rounded corners
}
.arrow::before,
.arrow::after {
border-width: ($popover-arrow-width / 2) $popover-arrow-height ($popover-arrow-width / 2) 0;
}
.arrow::before {
left: 0;
border-right-color: $popover-arrow-outer-color;
}
.arrow::after {
left: $popover-border-width;
border-right-color: $popover-arrow-color;
}
}
.bs-popover-bottom {
margin-top: $popover-arrow-height;
.arrow {
top: calc((#{$popover-arrow-height} + #{$popover-border-width}) * -1);
}
.arrow::before,
.arrow::after {
border-width: 0 ($popover-arrow-width / 2) $popover-arrow-height ($popover-arrow-width / 2);
}
.arrow::before {
top: 0;
border-bottom-color: $popover-arrow-outer-color;
}
.arrow::after {
top: $popover-border-width;
border-bottom-color: $popover-arrow-color;
}
// This will remove the popover-header's border just below the arrow
.popover-header::before {
position: absolute;
top: 0;
left: 50%;
display: block;
width: $popover-arrow-width;
margin-left: -$popover-arrow-width / 2;
content: "";
border-bottom: $popover-border-width solid $popover-header-bg;
}
}
.bs-popover-left {
margin-right: $popover-arrow-height;
.arrow {
right: calc((#{$popover-arrow-height} + #{$popover-border-width}) * -1);
width: $popover-arrow-height;
height: $popover-arrow-width;
margin: $border-radius-lg 0; // make sure the arrow does not touch the popover's rounded corners
}
.arrow::before,
.arrow::after {
border-width: ($popover-arrow-width / 2) 0 ($popover-arrow-width / 2) $popover-arrow-height;
}
.arrow::before {
right: 0;
border-left-color: $popover-arrow-outer-color;
}
.arrow::after {
right: $popover-border-width;
border-left-color: $popover-arrow-color;
}
}
.bs-popover-auto {
&[x-placement^="top"] {
@extend .bs-popover-top;
}
&[x-placement^="right"] {
@extend .bs-popover-right;
}
&[x-placement^="bottom"] {
@extend .bs-popover-bottom;
}
&[x-placement^="left"] {
@extend .bs-popover-left;
}
}
// Offset the popover to account for the popover arrow
.popover-header {
padding: $popover-header-padding-y $popover-header-padding-x;
margin-bottom: 0; // Reset the default from Reboot
font-size: $font-size-base;
color: $popover-header-color;
background-color: $popover-header-bg;
border-bottom: $popover-border-width solid darken($popover-header-bg, 5%);
$offset-border-width: calc(#{$border-radius-lg} - #{$popover-border-width});
@include border-top-radius($offset-border-width);
&:empty {
display: none;
}
}
.popover-body {
padding: $popover-body-padding-y $popover-body-padding-x;
color: $popover-body-color;
}
+141
View File
@@ -0,0 +1,141 @@
// stylelint-disable declaration-no-important, selector-no-qualifying-type
// Source: https://github.com/h5bp/html5-boilerplate/blob/master/src/css/main.css
// ==========================================================================
// Print styles.
// Inlined to avoid the additional HTTP request:
// https://www.phpied.com/delay-loading-your-print-css/
// ==========================================================================
@if $enable-print-styles {
@media print {
*,
*::before,
*::after {
// Bootstrap specific; comment out `color` and `background`
//color: $black !important; // Black prints faster
text-shadow: none !important;
//background: transparent !important;
box-shadow: none !important;
}
a {
&:not(.btn) {
text-decoration: underline;
}
}
// Bootstrap specific; comment the following selector out
//a[href]::after {
// content: " (" attr(href) ")";
//}
abbr[title]::after {
content: " (" attr(title) ")";
}
// Bootstrap specific; comment the following selector out
//
// Don't show links that are fragment identifiers,
// or use the `javascript:` pseudo protocol
//
//a[href^="#"]::after,
//a[href^="javascript:"]::after {
// content: "";
//}
pre {
white-space: pre-wrap !important;
}
pre,
blockquote {
border: $border-width solid $gray-500; // Bootstrap custom code; using `$border-width` instead of 1px
page-break-inside: avoid;
}
//
// Printing Tables:
// http://css-discuss.incutio.com/wiki/Printing_Tables
//
thead {
display: table-header-group;
}
tr,
img {
page-break-inside: avoid;
}
p,
h2,
h3 {
orphans: 3;
widows: 3;
}
h2,
h3 {
page-break-after: avoid;
}
// Bootstrap specific changes start
// Specify a size and min-width to make printing closer across browsers.
// We don't set margin here because it breaks `size` in Chrome. We also
// don't use `!important` on `size` as it breaks in Chrome.
@page {
size: $print-page-size;
}
body {
min-width: $print-body-min-width !important;
}
.container {
min-width: $print-body-min-width !important;
}
// Bootstrap components
.navbar {
display: none;
}
.badge {
border: $border-width solid $black;
}
.table {
border-collapse: collapse !important;
td,
th {
background-color: $white !important;
}
}
.table-bordered {
th,
td {
border: 1px solid $gray-300 !important;
}
}
.table-dark {
color: inherit;
th,
td,
thead th,
tbody + tbody {
border-color: $table-border-color;
}
}
.table .thead-dark th {
color: inherit;
border-color: $table-border-color;
}
// Bootstrap specific changes end
}
}
@@ -0,0 +1,34 @@
@keyframes progress-bar-stripes {
from { background-position: $progress-height 0; }
to { background-position: 0 0; }
}
.progress {
display: flex;
height: $progress-height;
overflow: hidden; // force rounded corners by cropping it
font-size: $progress-font-size;
background-color: $progress-bg;
@include border-radius($progress-border-radius);
@include box-shadow($progress-box-shadow);
}
.progress-bar {
display: flex;
flex-direction: column;
justify-content: center;
color: $progress-bar-color;
text-align: center;
white-space: nowrap;
background-color: $progress-bar-bg;
@include transition($progress-bar-transition);
}
.progress-bar-striped {
@include gradient-striped();
background-size: $progress-height $progress-height;
}
.progress-bar-animated {
animation: progress-bar-stripes $progress-bar-animation-timing;
}
+462
View File
@@ -0,0 +1,462 @@
// stylelint-disable at-rule-no-vendor-prefix, declaration-no-important, selector-no-qualifying-type, property-no-vendor-prefix
// Reboot
//
// Normalization of HTML elements, manually forked from Normalize.css to remove
// styles targeting irrelevant browsers while applying new styles.
//
// Normalize is licensed MIT. https://github.com/necolas/normalize.css
// Document
//
// 1. Change from `box-sizing: content-box` so that `width` is not affected by `padding` or `border`.
// 2. Change the default font family in all browsers.
// 3. Correct the line height in all browsers.
// 4. Prevent adjustments of font size after orientation changes in IE on Windows Phone and in iOS.
// 5. Change the default tap highlight to be completely transparent in iOS.
*,
*::before,
*::after {
box-sizing: border-box; // 1
}
html {
font-family: sans-serif; // 2
line-height: 1.15; // 3
-webkit-text-size-adjust: 100%; // 4
-webkit-tap-highlight-color: rgba($black, 0); // 5
}
// Shim for "new" HTML5 structural elements to display correctly (IE10, older browsers)
// TODO: remove in v5
// stylelint-disable-next-line selector-list-comma-newline-after
article, aside, figcaption, figure, footer, header, hgroup, main, nav, section {
display: block;
}
// Body
//
// 1. Remove the margin in all browsers.
// 2. As a best practice, apply a default `background-color`.
// 3. Set an explicit initial text-align value so that we can later use
// the `inherit` value on things like `<th>` elements.
body {
margin: 0; // 1
font-family: $font-family-base;
font-size: $font-size-base;
font-weight: $font-weight-base;
line-height: $line-height-base;
color: $body-color;
text-align: left; // 3
background-color: $body-bg; // 2
}
// Suppress the focus outline on elements that cannot be accessed via keyboard.
// This prevents an unwanted focus outline from appearing around elements that
// might still respond to pointer events.
//
// Credit: https://github.com/suitcss/base
[tabindex="-1"]:focus {
outline: 0 !important;
}
// Content grouping
//
// 1. Add the correct box sizing in Firefox.
// 2. Show the overflow in Edge and IE.
hr {
box-sizing: content-box; // 1
height: 0; // 1
overflow: visible; // 2
}
//
// Typography
//
// Remove top margins from headings
//
// By default, `<h1>`-`<h6>` all receive top and bottom margins. We nuke the top
// margin for easier control within type scales as it avoids margin collapsing.
// stylelint-disable-next-line selector-list-comma-newline-after
h1, h2, h3, h4, h5, h6 {
margin-top: 0;
margin-bottom: $headings-margin-bottom;
}
// Reset margins on paragraphs
//
// Similarly, the top margin on `<p>`s get reset. However, we also reset the
// bottom margin to use `rem` units instead of `em`.
p {
margin-top: 0;
margin-bottom: $paragraph-margin-bottom;
}
// Abbreviations
//
// 1. Duplicate behavior to the data-* attribute for our tooltip plugin
// 2. Add the correct text decoration in Chrome, Edge, IE, Opera, and Safari.
// 3. Add explicit cursor to indicate changed behavior.
// 4. Remove the bottom border in Firefox 39-.
// 5. Prevent the text-decoration to be skipped.
abbr[title],
abbr[data-original-title] { // 1
text-decoration: underline; // 2
text-decoration: underline dotted; // 2
cursor: help; // 3
border-bottom: 0; // 4
text-decoration-skip-ink: none; // 5
}
address {
margin-bottom: 1rem;
font-style: normal;
line-height: inherit;
}
ol,
ul,
dl {
margin-top: 0;
margin-bottom: 1rem;
}
ol ol,
ul ul,
ol ul,
ul ol {
margin-bottom: 0;
}
dt {
font-weight: $dt-font-weight;
}
dd {
margin-bottom: .5rem;
margin-left: 0; // Undo browser default
}
blockquote {
margin: 0 0 1rem;
}
b,
strong {
font-weight: $font-weight-bolder; // Add the correct font weight in Chrome, Edge, and Safari
}
small {
font-size: 80%; // Add the correct font size in all browsers
}
//
// Prevent `sub` and `sup` elements from affecting the line height in
// all browsers.
//
sub,
sup {
position: relative;
font-size: 75%;
line-height: 0;
vertical-align: baseline;
}
sub { bottom: -.25em; }
sup { top: -.5em; }
//
// Links
//
a {
color: $link-color;
text-decoration: $link-decoration;
background-color: transparent; // Remove the gray background on active links in IE 10.
@include hover {
color: $link-hover-color;
text-decoration: $link-hover-decoration;
}
}
// And undo these styles for placeholder links/named anchors (without href)
// which have not been made explicitly keyboard-focusable (without tabindex).
// It would be more straightforward to just use a[href] in previous block, but that
// causes specificity issues in many other styles that are too complex to fix.
// See https://github.com/twbs/bootstrap/issues/19402
a:not([href]):not([tabindex]) {
color: inherit;
text-decoration: none;
@include hover-focus {
color: inherit;
text-decoration: none;
}
&:focus {
outline: 0;
}
}
//
// Code
//
pre,
code,
kbd,
samp {
font-family: $font-family-monospace;
font-size: 1em; // Correct the odd `em` font sizing in all browsers.
}
pre {
// Remove browser default top margin
margin-top: 0;
// Reset browser default of `1em` to use `rem`s
margin-bottom: 1rem;
// Don't allow content to break outside
overflow: auto;
}
//
// Figures
//
figure {
// Apply a consistent margin strategy (matches our type styles).
margin: 0 0 1rem;
}
//
// Images and content
//
img {
vertical-align: middle;
border-style: none; // Remove the border on images inside links in IE 10-.
}
svg {
// Workaround for the SVG overflow bug in IE10/11 is still required.
// See https://github.com/twbs/bootstrap/issues/26878
overflow: hidden;
vertical-align: middle;
}
//
// Tables
//
table {
border-collapse: collapse; // Prevent double borders
}
caption {
padding-top: $table-cell-padding;
padding-bottom: $table-cell-padding;
color: $table-caption-color;
text-align: left;
caption-side: bottom;
}
th {
// Matches default `<td>` alignment by inheriting from the `<body>`, or the
// closest parent with a set `text-align`.
text-align: inherit;
}
//
// Forms
//
label {
// Allow labels to use `margin` for spacing.
display: inline-block;
margin-bottom: $label-margin-bottom;
}
// Remove the default `border-radius` that macOS Chrome adds.
//
// Details at https://github.com/twbs/bootstrap/issues/24093
button {
border-radius: 0;
}
// Work around a Firefox/IE bug where the transparent `button` background
// results in a loss of the default `button` focus styles.
//
// Credit: https://github.com/suitcss/base/
button:focus {
outline: 1px dotted;
outline: 5px auto -webkit-focus-ring-color;
}
input,
button,
select,
optgroup,
textarea {
margin: 0; // Remove the margin in Firefox and Safari
font-family: inherit;
font-size: inherit;
line-height: inherit;
}
button,
input {
overflow: visible; // Show the overflow in Edge
}
button,
select {
text-transform: none; // Remove the inheritance of text transform in Firefox
}
// 1. Prevent a WebKit bug where (2) destroys native `audio` and `video`
// controls in Android 4.
// 2. Correct the inability to style clickable types in iOS and Safari.
button,
[type="button"], // 1
[type="reset"],
[type="submit"] {
-webkit-appearance: button; // 2
}
// Remove inner border and padding from Firefox, but don't restore the outline like Normalize.
button::-moz-focus-inner,
[type="button"]::-moz-focus-inner,
[type="reset"]::-moz-focus-inner,
[type="submit"]::-moz-focus-inner {
padding: 0;
border-style: none;
}
input[type="radio"],
input[type="checkbox"] {
box-sizing: border-box; // 1. Add the correct box sizing in IE 10-
padding: 0; // 2. Remove the padding in IE 10-
}
input[type="date"],
input[type="time"],
input[type="datetime-local"],
input[type="month"] {
// Remove the default appearance of temporal inputs to avoid a Mobile Safari
// bug where setting a custom line-height prevents text from being vertically
// centered within the input.
// See https://bugs.webkit.org/show_bug.cgi?id=139848
// and https://github.com/twbs/bootstrap/issues/11266
-webkit-appearance: listbox;
}
textarea {
overflow: auto; // Remove the default vertical scrollbar in IE.
// Textareas should really only resize vertically so they don't break their (horizontal) containers.
resize: vertical;
}
fieldset {
// Browsers set a default `min-width: min-content;` on fieldsets,
// unlike e.g. `<div>`s, which have `min-width: 0;` by default.
// So we reset that to ensure fieldsets behave more like a standard block element.
// See https://github.com/twbs/bootstrap/issues/12359
// and https://html.spec.whatwg.org/multipage/#the-fieldset-and-legend-elements
min-width: 0;
// Reset the default outline behavior of fieldsets so they don't affect page layout.
padding: 0;
margin: 0;
border: 0;
}
// 1. Correct the text wrapping in Edge and IE.
// 2. Correct the color inheritance from `fieldset` elements in IE.
legend {
display: block;
width: 100%;
max-width: 100%; // 1
padding: 0;
margin-bottom: .5rem;
font-size: 1.5rem;
line-height: inherit;
color: inherit; // 2
white-space: normal; // 1
}
progress {
vertical-align: baseline; // Add the correct vertical alignment in Chrome, Firefox, and Opera.
}
// Correct the cursor style of increment and decrement buttons in Chrome.
[type="number"]::-webkit-inner-spin-button,
[type="number"]::-webkit-outer-spin-button {
height: auto;
}
[type="search"] {
// This overrides the extra rounded corners on search inputs in iOS so that our
// `.form-control` class can properly style them. Note that this cannot simply
// be added to `.form-control` as it's not specific enough. For details, see
// https://github.com/twbs/bootstrap/issues/11586.
outline-offset: -2px; // 2. Correct the outline style in Safari.
-webkit-appearance: none;
}
//
// Remove the inner padding and cancel buttons in Chrome and Safari on macOS.
//
[type="search"]::-webkit-search-decoration {
-webkit-appearance: none;
}
//
// 1. Correct the inability to style clickable types in iOS and Safari.
// 2. Change font properties to `inherit` in Safari.
//
::-webkit-file-upload-button {
font: inherit; // 2
-webkit-appearance: button; // 1
}
//
// Correct element displays
//
output {
display: inline-block;
}
summary {
display: list-item; // Add the correct display in all browsers
cursor: pointer;
}
template {
display: none; // Add the correct display in IE
}
// Always hide an element with the `hidden` HTML attribute (from PureCSS).
// Needed for proper display in IE 10-.
[hidden] {
display: none !important;
}
+19
View File
@@ -0,0 +1,19 @@
:root {
// Custom variable values only support SassScript inside `#{}`.
@each $color, $value in $colors {
--#{$color}: #{$value};
}
@each $color, $value in $theme-colors {
--#{$color}: #{$value};
}
@each $bp, $value in $grid-breakpoints {
--breakpoint-#{$bp}: #{$value};
}
// Use `inspect` for lists so that quoted items keep the quotes.
// See https://github.com/sass/sass/issues/2383#issuecomment-336349172
--font-family-sans-serif: #{inspect($font-family-sans-serif)};
--font-family-monospace: #{inspect($font-family-monospace)};
}
@@ -0,0 +1,53 @@
//
// Rotating border
//
@keyframes spinner-border {
to { transform: rotate(360deg); }
}
.spinner-border {
display: inline-block;
width: $spinner-width;
height: $spinner-height;
vertical-align: text-bottom;
border: $spinner-border-width solid currentColor;
border-right-color: transparent;
border-radius: 50%;
animation: spinner-border .75s linear infinite;
}
.spinner-border-sm {
width: $spinner-width-sm;
height: $spinner-height-sm;
border-width: $spinner-border-width-sm;
}
//
// Growing circle
//
@keyframes spinner-grow {
0% {
transform: scale(0);
}
50% {
opacity: 1;
}
}
.spinner-grow {
display: inline-block;
width: $spinner-width;
height: $spinner-height;
vertical-align: text-bottom;
background-color: currentColor;
border-radius: 50%;
opacity: 0;
animation: spinner-grow .75s linear infinite;
}
.spinner-grow-sm {
width: $spinner-width-sm;
height: $spinner-height-sm;
}
+187
View File
@@ -0,0 +1,187 @@
//
// Basic Bootstrap table
//
.table {
width: 100%;
margin-bottom: $spacer;
background-color: $table-bg; // Reset for nesting within parents with `background-color`.
th,
td {
padding: $table-cell-padding;
vertical-align: top;
border-top: $table-border-width solid $table-border-color;
}
thead th {
vertical-align: bottom;
border-bottom: (2 * $table-border-width) solid $table-border-color;
}
tbody + tbody {
border-top: (2 * $table-border-width) solid $table-border-color;
}
.table {
background-color: $body-bg;
}
}
//
// Condensed table w/ half padding
//
.table-sm {
th,
td {
padding: $table-cell-padding-sm;
}
}
// Border versions
//
// Add or remove borders all around the table and between all the columns.
.table-bordered {
border: $table-border-width solid $table-border-color;
th,
td {
border: $table-border-width solid $table-border-color;
}
thead {
th,
td {
border-bottom-width: 2 * $table-border-width;
}
}
}
.table-borderless {
th,
td,
thead th,
tbody + tbody {
border: 0;
}
}
// Zebra-striping
//
// Default zebra-stripe styles (alternating gray and transparent backgrounds)
.table-striped {
tbody tr:nth-of-type(#{$table-striped-order}) {
background-color: $table-accent-bg;
}
}
// Hover effect
//
// Placed here since it has to come after the potential zebra striping
.table-hover {
tbody tr {
@include hover {
background-color: $table-hover-bg;
}
}
}
// Table backgrounds
//
// Exact selectors below required to override `.table-striped` and prevent
// inheritance to nested tables.
@each $color, $value in $theme-colors {
@include table-row-variant($color, theme-color-level($color, $table-bg-level), theme-color-level($color, $table-border-level));
}
@include table-row-variant(active, $table-active-bg);
// Dark styles
//
// Same table markup, but inverted color scheme: dark background and light text.
// stylelint-disable-next-line no-duplicate-selectors
.table {
.thead-dark {
th {
color: $table-dark-color;
background-color: $table-dark-bg;
border-color: $table-dark-border-color;
}
}
.thead-light {
th {
color: $table-head-color;
background-color: $table-head-bg;
border-color: $table-border-color;
}
}
}
.table-dark {
color: $table-dark-color;
background-color: $table-dark-bg;
th,
td,
thead th {
border-color: $table-dark-border-color;
}
&.table-bordered {
border: 0;
}
&.table-striped {
tbody tr:nth-of-type(odd) {
background-color: $table-dark-accent-bg;
}
}
&.table-hover {
tbody tr {
@include hover {
background-color: $table-dark-hover-bg;
}
}
}
}
// Responsive tables
//
// Generate series of `.table-responsive-*` classes for configuring the screen
// size of where your table will overflow.
.table-responsive {
@each $breakpoint in map-keys($grid-breakpoints) {
$next: breakpoint-next($breakpoint, $grid-breakpoints);
$infix: breakpoint-infix($next, $grid-breakpoints);
&#{$infix} {
@include media-breakpoint-down($breakpoint) {
display: block;
width: 100%;
overflow-x: auto;
-webkit-overflow-scrolling: touch;
-ms-overflow-style: -ms-autohiding-scrollbar; // See https://github.com/twbs/bootstrap/pull/10057
// Prevent double border on horizontal scroll due to use of `display: block;`
> .table-bordered {
border: 0;
}
}
}
}
}
+43
View File
@@ -0,0 +1,43 @@
.toast {
max-width: $toast-max-width;
overflow: hidden; // cheap rounded corners on nested items
font-size: $toast-font-size; // knock it down to 14px
background-color: $toast-background-color;
background-clip: padding-box;
border: $toast-border-width solid $toast-border-color;
border-radius: $toast-border-radius;
box-shadow: $toast-box-shadow;
backdrop-filter: blur(10px);
opacity: 0;
&:not(:last-child) {
margin-bottom: $toast-padding-x;
}
&.showing {
opacity: 1;
}
&.show {
display: block;
opacity: 1;
}
&.hide {
display: none;
}
}
.toast-header {
display: flex;
align-items: center;
padding: $toast-padding-y $toast-padding-x;
color: $toast-header-color;
background-color: $toast-header-background-color;
background-clip: padding-box;
border-bottom: $toast-border-width solid $toast-header-border-color;
}
.toast-body {
padding: $toast-padding-x; // apply to both vertical and horizontal
}
+115
View File
@@ -0,0 +1,115 @@
// Base class
.tooltip {
position: absolute;
z-index: $zindex-tooltip;
display: block;
margin: $tooltip-margin;
// Our parent element can be arbitrary since tooltips are by default inserted as a sibling of their target element.
// So reset our font and text properties to avoid inheriting weird values.
@include reset-text();
font-size: $tooltip-font-size;
// Allow breaking very long words so they don't overflow the tooltip's bounds
word-wrap: break-word;
opacity: 0;
&.show { opacity: $tooltip-opacity; }
.arrow {
position: absolute;
display: block;
width: $tooltip-arrow-width;
height: $tooltip-arrow-height;
&::before {
position: absolute;
content: "";
border-color: transparent;
border-style: solid;
}
}
}
.bs-tooltip-top {
padding: $tooltip-arrow-height 0;
.arrow {
bottom: 0;
&::before {
top: 0;
border-width: $tooltip-arrow-height ($tooltip-arrow-width / 2) 0;
border-top-color: $tooltip-arrow-color;
}
}
}
.bs-tooltip-right {
padding: 0 $tooltip-arrow-height;
.arrow {
left: 0;
width: $tooltip-arrow-height;
height: $tooltip-arrow-width;
&::before {
right: 0;
border-width: ($tooltip-arrow-width / 2) $tooltip-arrow-height ($tooltip-arrow-width / 2) 0;
border-right-color: $tooltip-arrow-color;
}
}
}
.bs-tooltip-bottom {
padding: $tooltip-arrow-height 0;
.arrow {
top: 0;
&::before {
bottom: 0;
border-width: 0 ($tooltip-arrow-width / 2) $tooltip-arrow-height;
border-bottom-color: $tooltip-arrow-color;
}
}
}
.bs-tooltip-left {
padding: 0 $tooltip-arrow-height;
.arrow {
right: 0;
width: $tooltip-arrow-height;
height: $tooltip-arrow-width;
&::before {
left: 0;
border-width: ($tooltip-arrow-width / 2) 0 ($tooltip-arrow-width / 2) $tooltip-arrow-height;
border-left-color: $tooltip-arrow-color;
}
}
}
.bs-tooltip-auto {
&[x-placement^="top"] {
@extend .bs-tooltip-top;
}
&[x-placement^="right"] {
@extend .bs-tooltip-right;
}
&[x-placement^="bottom"] {
@extend .bs-tooltip-bottom;
}
&[x-placement^="left"] {
@extend .bs-tooltip-left;
}
}
// Wrapper for the tooltip content
.tooltip-inner {
max-width: $tooltip-max-width;
padding: $tooltip-padding-y $tooltip-padding-x;
color: $tooltip-color;
text-align: center;
background-color: $tooltip-bg;
@include border-radius($tooltip-border-radius);
}
@@ -0,0 +1,22 @@
// stylelint-disable selector-no-qualifying-type
.fade {
@include transition($transition-fade);
&:not(.show) {
opacity: 0;
}
}
.collapse {
&:not(.show) {
display: none;
}
}
.collapsing {
position: relative;
height: 0;
overflow: hidden;
@include transition($transition-collapse);
}
+125
View File
@@ -0,0 +1,125 @@
// stylelint-disable declaration-no-important, selector-list-comma-newline-after
//
// Headings
//
h1, h2, h3, h4, h5, h6,
.h1, .h2, .h3, .h4, .h5, .h6 {
margin-bottom: $headings-margin-bottom;
font-family: $headings-font-family;
font-weight: $headings-font-weight;
line-height: $headings-line-height;
color: $headings-color;
}
h1, .h1 { font-size: $h1-font-size; }
h2, .h2 { font-size: $h2-font-size; }
h3, .h3 { font-size: $h3-font-size; }
h4, .h4 { font-size: $h4-font-size; }
h5, .h5 { font-size: $h5-font-size; }
h6, .h6 { font-size: $h6-font-size; }
.lead {
font-size: $lead-font-size;
font-weight: $lead-font-weight;
}
// Type display classes
.display-1 {
font-size: $display1-size;
font-weight: $display1-weight;
line-height: $display-line-height;
}
.display-2 {
font-size: $display2-size;
font-weight: $display2-weight;
line-height: $display-line-height;
}
.display-3 {
font-size: $display3-size;
font-weight: $display3-weight;
line-height: $display-line-height;
}
.display-4 {
font-size: $display4-size;
font-weight: $display4-weight;
line-height: $display-line-height;
}
//
// Horizontal rules
//
hr {
margin-top: $hr-margin-y;
margin-bottom: $hr-margin-y;
border: 0;
border-top: $hr-border-width solid $hr-border-color;
}
//
// Emphasis
//
small,
.small {
font-size: $small-font-size;
font-weight: $font-weight-normal;
}
mark,
.mark {
padding: $mark-padding;
background-color: $mark-bg;
}
//
// Lists
//
.list-unstyled {
@include list-unstyled;
}
// Inline turns list items into inline-block
.list-inline {
@include list-unstyled;
}
.list-inline-item {
display: inline-block;
&:not(:last-child) {
margin-right: $list-inline-padding;
}
}
//
// Misc
//
// Builds on `abbr`
.initialism {
font-size: 90%;
text-transform: uppercase;
}
// Blockquotes
.blockquote {
margin-bottom: $spacer;
font-size: $blockquote-font-size;
}
.blockquote-footer {
display: block;
font-size: $blockquote-small-font-size;
color: $blockquote-small-color;
&::before {
content: "\2014\00A0"; // em dash, nbsp
}
}
@@ -0,0 +1,16 @@
@import "utilities/align";
@import "utilities/background";
@import "utilities/borders";
@import "utilities/clearfix";
@import "utilities/display";
@import "utilities/embed";
@import "utilities/flex";
@import "utilities/float";
@import "utilities/overflow";
@import "utilities/position";
@import "utilities/screenreaders";
@import "utilities/shadows";
@import "utilities/sizing";
@import "utilities/spacing";
@import "utilities/text";
@import "utilities/visibility";
File diff suppressed because it is too large Load Diff
@@ -0,0 +1,29 @@
/*!
* Bootstrap Grid v4.2.1 (https://getbootstrap.com/)
* Copyright 2011-2018 The Bootstrap Authors
* Copyright 2011-2018 Twitter, Inc.
* Licensed under MIT (https://github.com/twbs/bootstrap/blob/master/LICENSE)
*/
html {
box-sizing: border-box;
-ms-overflow-style: scrollbar;
}
*,
*::before,
*::after {
box-sizing: inherit;
}
@import "functions";
@import "variables";
@import "mixins/breakpoints";
@import "mixins/grid-framework";
@import "mixins/grid";
@import "grid";
@import "utilities/display";
@import "utilities/flex";
@import "utilities/spacing";
@@ -0,0 +1,12 @@
/*!
* Bootstrap Reboot v4.2.1 (https://getbootstrap.com/)
* Copyright 2011-2018 The Bootstrap Authors
* Copyright 2011-2018 Twitter, Inc.
* Licensed under MIT (https://github.com/twbs/bootstrap/blob/master/LICENSE)
* Forked from Normalize.css, licensed MIT (https://github.com/necolas/normalize.css/blob/master/LICENSE.md)
*/
@import "functions";
@import "variables";
@import "mixins";
@import "reboot";
@@ -0,0 +1,44 @@
/*!
* Bootstrap v4.2.1 (https://getbootstrap.com/)
* Copyright 2011-2018 The Bootstrap Authors
* Copyright 2011-2018 Twitter, Inc.
* Licensed under MIT (https://github.com/twbs/bootstrap/blob/master/LICENSE)
*/
@import "functions";
@import "variables";
@import "mixins";
@import "root";
@import "reboot";
@import "type";
@import "images";
@import "code";
@import "grid";
@import "tables";
@import "forms";
@import "buttons";
@import "transitions";
@import "dropdown";
@import "button-group";
@import "input-group";
@import "custom-forms";
@import "nav";
@import "navbar";
@import "card";
@import "breadcrumb";
@import "pagination";
@import "badge";
@import "jumbotron";
@import "alert";
@import "progress";
@import "media";
@import "list-group";
@import "close";
@import "toasts";
@import "modal";
@import "tooltip";
@import "popover";
@import "carousel";
@import "spinners";
@import "utilities";
@import "print";
@@ -0,0 +1,13 @@
@mixin alert-variant($background, $border, $color) {
color: $color;
@include gradient-bg($background);
border-color: $border;
hr {
border-top-color: darken($border, 5%);
}
.alert-link {
color: darken($color, 10%);
}
}
@@ -0,0 +1,21 @@
// stylelint-disable declaration-no-important
// Contextual backgrounds
@mixin bg-variant($parent, $color) {
#{$parent} {
background-color: $color !important;
}
a#{$parent},
button#{$parent} {
@include hover-focus {
background-color: darken($color, 10%) !important;
}
}
}
@mixin bg-gradient-variant($parent, $color) {
#{$parent} {
background: $color linear-gradient(180deg, mix($body-bg, $color, 15%), $color) repeat-x !important;
}
}
@@ -0,0 +1,11 @@
@mixin badge-variant($bg) {
color: color-yiq($bg);
background-color: $bg;
@at-root a#{&} {
@include hover-focus {
color: color-yiq($bg);
background-color: darken($bg, 10%);
}
}
}
@@ -0,0 +1,35 @@
// Single side border-radius
@mixin border-radius($radius: $border-radius) {
@if $enable-rounded {
border-radius: $radius;
}
}
@mixin border-top-radius($radius) {
@if $enable-rounded {
border-top-left-radius: $radius;
border-top-right-radius: $radius;
}
}
@mixin border-right-radius($radius) {
@if $enable-rounded {
border-top-right-radius: $radius;
border-bottom-right-radius: $radius;
}
}
@mixin border-bottom-radius($radius) {
@if $enable-rounded {
border-bottom-right-radius: $radius;
border-bottom-left-radius: $radius;
}
}
@mixin border-left-radius($radius) {
@if $enable-rounded {
border-top-left-radius: $radius;
border-bottom-left-radius: $radius;
}
}

Some files were not shown because too many files have changed in this diff Show More