New improved width system, and support for SVG graphics
This commit is contained in:
@@ -1,6 +1,5 @@
|
||||
<template>
|
||||
<div id="app">
|
||||
|
||||
<router-view/>
|
||||
<Footer />
|
||||
</div>
|
||||
|
||||
@@ -70,7 +70,7 @@ export default {
|
||||
<style scoped lang="scss">
|
||||
|
||||
.collapsable {
|
||||
width: 310px;
|
||||
// width: 310px;
|
||||
padding: 5px;
|
||||
margin: 10px;
|
||||
border-radius: 10px;
|
||||
@@ -78,12 +78,12 @@ export default {
|
||||
background: -webkit-linear-gradient(to left top, #9F86FF, #1CA8DD, #007AE1);
|
||||
background: linear-gradient(to left top, #9F86FF, #1CA8DD, #007AE1);
|
||||
box-shadow: 1px 1px 2px #130f23;
|
||||
|
||||
&.col-1 { width: 155px; }
|
||||
&.col-2 { width: 310px; }
|
||||
&.col-3 { width: 465px; }
|
||||
&.col-4 { width: 620px; }
|
||||
&.col-5 { width: 775px; }
|
||||
width: auto;
|
||||
// &.col-1 { width: 155px; }
|
||||
// &.col-2 { width: 310px; }
|
||||
// &.col-3 { width: 465px; }
|
||||
// &.col-4 { width: 620px; }
|
||||
// &.col-5 { width: 775px; }
|
||||
|
||||
.wrap-collabsible {
|
||||
margin-bottom: 1.2rem 0;
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
<template>
|
||||
<el-tooltip placement="bottom" effect="dark" :content="description" :disabled="!description">
|
||||
<div :class="`item ${!icon? 'short': ''}`" tabindex="0">
|
||||
<div :class="`item ${!icon && !svg? 'short': ''}`" tabindex="0">
|
||||
<div class="tile-title" :id="`tile-${id}`">
|
||||
<span class="text">{{ title }}</span>
|
||||
<div class="overflow-dots">...</div>
|
||||
@@ -10,6 +10,11 @@
|
||||
:src="`/img/tile-icons/${icon}.png`"
|
||||
class="tile-icon"
|
||||
/>
|
||||
<img
|
||||
v-else-if="svg"
|
||||
:src="`/img/tile-svgs/${svg}.svg`"
|
||||
class="tile-svg"
|
||||
/>
|
||||
</div>
|
||||
</el-tooltip>
|
||||
</template>
|
||||
@@ -24,6 +29,7 @@ export default {
|
||||
subtitle: String, // Optional sub-text
|
||||
description: String, // Optional tooltip hover text
|
||||
icon: String, // Optional path to icon, within public/img/tile-icons
|
||||
svg: String, // Optional vector graphic, that is then dynamically filled
|
||||
color: String, // Optional background color, specified in hex code
|
||||
url: String, // URL to the resource, optional but recommended
|
||||
openingMethod: { // Where resource will open, either 'newtab', 'sametab' or 'iframe'
|
||||
@@ -135,4 +141,14 @@ export default {
|
||||
width: 60px;
|
||||
}
|
||||
|
||||
.tile-svg {
|
||||
width: 56px;
|
||||
filter:
|
||||
invert(69%)
|
||||
sepia(40%)
|
||||
saturate(4686%)
|
||||
hue-rotate(142deg)
|
||||
brightness(96%)
|
||||
contrast(102%);
|
||||
}
|
||||
</style>
|
||||
|
||||
@@ -11,6 +11,7 @@
|
||||
:title="item.title"
|
||||
:description="item.description"
|
||||
:icon="item.icon"
|
||||
:svg="item.svg"
|
||||
/>
|
||||
</div>
|
||||
</Collapsable>
|
||||
|
||||
@@ -287,5 +287,43 @@
|
||||
"url": "https://online.sjdaccountancy.com/dashboard"
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"id": "008",
|
||||
"name": "Social Profiles",
|
||||
"collapsed": true,
|
||||
"cols": 1,
|
||||
"items": [
|
||||
{
|
||||
"id":"001",
|
||||
"title": "Twitter",
|
||||
"svg": "004-twitter",
|
||||
"url": "https://twitter.com/Lissy_Sykes"
|
||||
},
|
||||
{
|
||||
"id":"002",
|
||||
"title": "Instagram",
|
||||
"svg": "014-instagram",
|
||||
"url": "https://www.instagram.com/lissy_sykes93/"
|
||||
},
|
||||
{
|
||||
"id":"003",
|
||||
"title": "Facebook",
|
||||
"svg": "013-facebook",
|
||||
"url": "https://fb.com/liss.sykes"
|
||||
},
|
||||
{
|
||||
"id":"004",
|
||||
"title": "LinkedIn",
|
||||
"svg": "015-linkedin",
|
||||
"url": "https://www.linkedin.com/in/aliciasykes/"
|
||||
},
|
||||
{
|
||||
"id":"005",
|
||||
"title": "VK",
|
||||
"icon": "",
|
||||
"url": "https://vk.com/id554636914"
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
225
src/lib/vue-masonry-css.js
Normal file
225
src/lib/vue-masonry-css.js
Normal file
@@ -0,0 +1,225 @@
|
||||
// the component name `<masonry />`
|
||||
// can be overridden with `Vue.use(Masonry, { name: 'the-masonry' });`
|
||||
const componentName = 'masonry';
|
||||
|
||||
const props = {
|
||||
tag: {
|
||||
type: [String],
|
||||
default: 'div'
|
||||
},
|
||||
cols: {
|
||||
type: [Object, Number, String],
|
||||
default: 2
|
||||
},
|
||||
gutter: {
|
||||
type: [Object, Number, String],
|
||||
default: 0
|
||||
},
|
||||
css: {
|
||||
type: [Boolean],
|
||||
default: true
|
||||
},
|
||||
columnTag: {
|
||||
type: [String],
|
||||
default: 'div'
|
||||
},
|
||||
columnClass: {
|
||||
type: [String, Array, Object],
|
||||
default: () => []
|
||||
},
|
||||
columnAttr: {
|
||||
type: [Object],
|
||||
default: () => ({})
|
||||
}
|
||||
};
|
||||
|
||||
// Get the resulting value from `:col=` prop
|
||||
// based on the window width
|
||||
const breakpointValue = (mixed, windowWidth) => {
|
||||
const valueAsNum = parseInt(mixed);
|
||||
|
||||
if(valueAsNum > -1) {
|
||||
return mixed;
|
||||
}else if(typeof mixed !== 'object') {
|
||||
return 0;
|
||||
}
|
||||
|
||||
let matchedBreakpoint = Infinity;
|
||||
let matchedValue = mixed.default || 0;
|
||||
|
||||
for(let k in mixed) {
|
||||
const breakpoint = parseInt(k);
|
||||
const breakpointValRaw = mixed[breakpoint];
|
||||
const breakpointVal = parseInt(breakpointValRaw);
|
||||
|
||||
if(isNaN(breakpoint) || isNaN(breakpointVal)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
const isNewBreakpoint = windowWidth <= breakpoint && breakpoint < matchedBreakpoint;
|
||||
|
||||
if(isNewBreakpoint) {
|
||||
matchedBreakpoint = breakpoint;
|
||||
matchedValue = breakpointValRaw;
|
||||
}
|
||||
}
|
||||
|
||||
return matchedValue;
|
||||
}
|
||||
|
||||
const component = {
|
||||
props,
|
||||
|
||||
data() {
|
||||
return {
|
||||
displayColumns: 2,
|
||||
displayGutter: 0
|
||||
}
|
||||
},
|
||||
|
||||
mounted() {
|
||||
this.$nextTick(() => {
|
||||
this.reCalculate();
|
||||
});
|
||||
|
||||
// Bind resize handler to page
|
||||
if(window) {
|
||||
window.addEventListener('resize', this.reCalculate);
|
||||
}
|
||||
},
|
||||
|
||||
updated() {
|
||||
this.$nextTick(() => {
|
||||
this.reCalculate();
|
||||
});
|
||||
},
|
||||
|
||||
beforeDestroy() {
|
||||
if(window) {
|
||||
window.removeEventListener('resize', this.reCalculate);
|
||||
}
|
||||
},
|
||||
|
||||
methods: {
|
||||
// Recalculate how many columns to display based on window width
|
||||
// and the value of the passed `:cols=` prop
|
||||
reCalculate() {
|
||||
const previousWindowWidth = this.windowWidth;
|
||||
|
||||
this.windowWidth = (window ? window.innerWidth : null) || Infinity;
|
||||
|
||||
// Window resize events get triggered on page height
|
||||
// change which when loading the page can result in multiple
|
||||
// needless calculations. We prevent this here.
|
||||
if(previousWindowWidth === this.windowWidth) {
|
||||
return;
|
||||
}
|
||||
|
||||
this._reCalculateColumnCount(this.windowWidth);
|
||||
|
||||
this._reCalculateGutterSize(this.windowWidth);
|
||||
},
|
||||
|
||||
_reCalculateGutterSize(windowWidth) {
|
||||
this.displayGutter = breakpointValue(this.gutter, windowWidth);
|
||||
},
|
||||
|
||||
_reCalculateColumnCount(windowWidth) {
|
||||
let newColumns = breakpointValue(this.cols, windowWidth);
|
||||
|
||||
// Make sure we can return a valid value
|
||||
newColumns = Math.max(1, Number(newColumns) || 0);
|
||||
|
||||
this.displayColumns = newColumns;
|
||||
},
|
||||
|
||||
_getChildItemsInColumnsArray() {
|
||||
const columns = [];
|
||||
let childItems = this.$slots.default || [];
|
||||
|
||||
// This component does not work with a child <transition-group /> ..yet,
|
||||
// so for now we think it may be helpful to ignore until we can find a way for support
|
||||
if(childItems.length === 1 && childItems[0].componentOptions && childItems[0].componentOptions.tag == 'transition-group') {
|
||||
childItems = childItems[0].componentOptions.children;
|
||||
}
|
||||
|
||||
// Loop through child elements
|
||||
for (let i = 0, visibleItemI = 0; i < childItems.length; i++, visibleItemI++) {
|
||||
// skip Vue elements without tags, which includes
|
||||
// whitespace elements and also plain text
|
||||
if(!childItems[i].tag) {
|
||||
visibleItemI--;
|
||||
|
||||
continue;
|
||||
}
|
||||
|
||||
// Get the column index the child item will end up in
|
||||
const columnIndex = visibleItemI % this.displayColumns;
|
||||
|
||||
if(!columns[columnIndex]) {
|
||||
columns[columnIndex] = [];
|
||||
}
|
||||
|
||||
columns[columnIndex].push(childItems[i]);
|
||||
}
|
||||
|
||||
return columns;
|
||||
}
|
||||
},
|
||||
|
||||
render(createElement) {
|
||||
const columnsContainingChildren = this._getChildItemsInColumnsArray();
|
||||
const isGutterSizeUnitless = parseInt(this.displayGutter) === this.displayGutter * 1;
|
||||
const gutterSizeWithUnit = isGutterSizeUnitless ? `${this.displayGutter}px` : this.displayGutter;
|
||||
|
||||
const columnStyle = {
|
||||
boxSizing: 'border-box',
|
||||
backgroundClip: 'padding-box',
|
||||
width: `${100 / this.displayColumns}%`,
|
||||
border: '0 solid transparent',
|
||||
borderLeftWidth: gutterSizeWithUnit
|
||||
};
|
||||
|
||||
const columns = columnsContainingChildren.map((children, index) => {
|
||||
/// Create column element and inject the children
|
||||
return createElement(this.columnTag, {
|
||||
key: index + '-' + columnsContainingChildren.length,
|
||||
style: this.css ? columnStyle : null,
|
||||
class: this.columnClass,
|
||||
attrs: this.columnAttr
|
||||
}, children); // specify child items here
|
||||
});
|
||||
|
||||
const containerStyle = {
|
||||
display: ['-webkit-box', '-ms-flexbox', 'flex'],
|
||||
marginLeft: `-${gutterSizeWithUnit}`
|
||||
};
|
||||
|
||||
// Return wrapper with columns
|
||||
return createElement(
|
||||
this.tag, // tag name
|
||||
this.css ? { style: containerStyle } : null, // element options
|
||||
columns // column vue elements
|
||||
);
|
||||
}
|
||||
};
|
||||
|
||||
const Plugin = function () {}
|
||||
|
||||
Plugin.install = function (Vue, options) {
|
||||
if (Plugin.installed) {
|
||||
return;
|
||||
}
|
||||
|
||||
if(options && options.name) {
|
||||
Vue.component(options.name, component);
|
||||
} else {
|
||||
Vue.component(componentName, component);
|
||||
}
|
||||
}
|
||||
|
||||
if (typeof window !== 'undefined' && window.Vue) {
|
||||
window.Vue.use(Plugin);
|
||||
}
|
||||
|
||||
export default Plugin;
|
||||
@@ -4,8 +4,10 @@ import 'element-ui/lib/theme-chalk/index.css';
|
||||
import App from './App.vue';
|
||||
import router from './router';
|
||||
import './registerServiceWorker';
|
||||
import VueMasonry from './lib/vue-masonry-css'; // Thank you @PaulCollett 🙌 https://git.io/JeeYC
|
||||
|
||||
Vue.use(Element);
|
||||
Vue.use(VueMasonry);
|
||||
Vue.config.productionTip = false;
|
||||
|
||||
new Vue({
|
||||
|
||||
@@ -4,15 +4,21 @@
|
||||
<FilterTile @user-is-searchin="searching" class="filter-container" />
|
||||
|
||||
<div class="item-group-container">
|
||||
<ItemGroup
|
||||
v-for="item in items"
|
||||
:key="item.id"
|
||||
:groupId="item.id"
|
||||
:title="item.name"
|
||||
:collapsed="item.collapsed"
|
||||
:cols="item.cols"
|
||||
:items="filterTiles(item.items)"
|
||||
/>
|
||||
|
||||
<masonry
|
||||
:cols="{600: 1, 780: 2, 1150: 2, 1780: 3, 9999: 4}"
|
||||
:gutter="30"
|
||||
>
|
||||
<ItemGroup
|
||||
v-for="item in items"
|
||||
:key="item.id"
|
||||
:groupId="item.id"
|
||||
:title="item.name"
|
||||
:collapsed="item.collapsed"
|
||||
:cols="item.cols"
|
||||
:items="filterTiles(item.items)"
|
||||
/>
|
||||
</masonry>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
Reference in New Issue
Block a user