Skip to content

Commit a81f3bc

Browse files
committed
feat: add CElementCover component
1 parent ab0ca35 commit a81f3bc

File tree

7 files changed

+204
-9
lines changed

7 files changed

+204
-9
lines changed
Lines changed: 92 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,92 @@
1+
<template>
2+
<div :style="outerContainerStyles">
3+
<div v-if="centeringWrapper" :style="centeringStyles">
4+
<slot>
5+
<CSpinner grow size="lg" color="primary"/>
6+
</slot>
7+
</div>
8+
<slot v-else>
9+
<CSpinner size="lg" color="primary"/>
10+
</slot>
11+
</div>
12+
</template>
13+
14+
<script>
15+
import CSpinner from '../spinner/CSpinner'
16+
17+
export default {
18+
name: 'CElementCover',
19+
props: {
20+
boundaries: Array,
21+
centeringWrapper: {
22+
type: Boolean,
23+
default: true
24+
},
25+
opacity: {
26+
type: Number,
27+
default: 0.4
28+
}
29+
},
30+
components: {
31+
CSpinner
32+
},
33+
data () {
34+
return {
35+
containerCoords: {
36+
top: 0,
37+
bottom: 0,
38+
left: 0,
39+
right: 0
40+
},
41+
parentCoords: null
42+
}
43+
},
44+
mounted () {
45+
if (this.boundaries) {
46+
this.setOffsets()
47+
}
48+
},
49+
computed: {
50+
outerContainerStyles () {
51+
return {
52+
...this.containerCoords,
53+
position: 'absolute',
54+
'background-color': `rgb(255,255,255,${this.opacity})`
55+
}
56+
},
57+
centeringStyles () {
58+
return {
59+
position: 'absolute',
60+
top: '50%',
61+
left: '50%',
62+
transform: 'translateX(-50%) translateY(-50%)'
63+
}
64+
}
65+
},
66+
methods: {
67+
setOffsets () {
68+
const parent = this.$el.parentElement
69+
this.parentCoords = parent.getBoundingClientRect()
70+
this.boundaries.forEach(cover => {
71+
const element = this.getElement(parent, cover.tag, cover.class)
72+
if (!element || !cover.sides) {
73+
return
74+
}
75+
cover.sides.forEach(side => {
76+
const sideMargin = Math.abs(element[side] - this.parentCoords[side])
77+
this.containerCoords[side] = sideMargin + 'px'
78+
})
79+
})
80+
},
81+
getElement (node, tag, styleClass) {
82+
let element = null
83+
if (styleClass) {
84+
element = node.getElementsByClassName(styleClass)[0]
85+
} else if (tag) {
86+
element = node.getElementsByTagName(tag)[0]
87+
}
88+
return element ? element.getBoundingClientRect() : null
89+
}
90+
}
91+
}
92+
</script>

src/components/element-cover/index.js

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
import CElementCover from './CElementCover'
2+
3+
export {
4+
CElementCover
5+
}
Lines changed: 50 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,50 @@
1+
// cannot do integration test without browser, due to
2+
// .getBoundingClientRect() always returning 0
3+
import { mount, createLocalVue } from '@vue/test-utils'
4+
import Component from '../CElementCover'
5+
6+
const localVue = new createLocalVue()
7+
const App = localVue.extend({
8+
components: {
9+
CElementCover: Component
10+
},
11+
render(h) {
12+
return h('div', {}, [
13+
h('div', { attrs: { class: 'first' }}),
14+
h('CElementCover', {
15+
props: {
16+
boundaries: [
17+
{
18+
sides: ['top', 'left'],
19+
class: 'first'
20+
},
21+
{
22+
sides: ['bottom'],
23+
tag: 'non-existing'
24+
},
25+
{
26+
sides: ['bottom']
27+
}
28+
]
29+
}
30+
})
31+
])
32+
}
33+
})
34+
35+
const customWrapper = mount(App, { localVue })
36+
const defaultWrapper = mount(Component)
37+
38+
const ComponentName = 'CElementCover'
39+
40+
describe(ComponentName, () => {
41+
it('has a name', () => {
42+
expect(Component.name).toBe(ComponentName)
43+
})
44+
it('renders correctly with default spinner', () => {
45+
expect(defaultWrapper.element).toMatchSnapshot()
46+
})
47+
it('renders correctly with custom content', () => {
48+
expect(customWrapper.element).toMatchSnapshot()
49+
})
50+
})
Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,40 @@
1+
// Jest Snapshot v1, https://goo.gl/fbAQLP
2+
3+
exports[`CElementCover renders correctly with custom content 1`] = `
4+
<div>
5+
<div
6+
class="first"
7+
/>
8+
<div
9+
style="top: 0px; bottom: 0px; left: 0px; right: 0px; position: absolute;"
10+
>
11+
<div
12+
style="position: absolute; top: 50%; left: 50%; transform: translateX(-50%) translateY(-50%);"
13+
>
14+
<div
15+
aria-hidden="false"
16+
aria-label="Loading"
17+
class="spinner-grow spinner-grow-lg text-primary"
18+
role="status"
19+
/>
20+
</div>
21+
</div>
22+
</div>
23+
`;
24+
25+
exports[`CElementCover renders correctly with default spinner 1`] = `
26+
<div
27+
style="top: 0px; bottom: 0px; left: 0px; right: 0px; position: absolute;"
28+
>
29+
<div
30+
style="position: absolute; top: 50%; left: 50%; transform: translateX(-50%) translateY(-50%);"
31+
>
32+
<div
33+
aria-hidden="false"
34+
aria-label="Loading"
35+
class="spinner-grow spinner-grow-lg text-primary"
36+
role="status"
37+
/>
38+
</div>
39+
</div>
40+
`;

src/components/index.d.ts

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -149,6 +149,13 @@ export declare class CDropdownHeader extends Vue {
149149

150150
export declare class CDropdownItem extends CLink {}
151151

152+
export declare class CElementCover extends Vue {
153+
boundaries: Array<{side: string[], tag?: string, class?: string}>
154+
centeringWrapper: boolean
155+
opacity: number
156+
}
157+
158+
152159
export declare class CEmbed extends Vue {
153160
type: string
154161
ratio: string

src/components/table/CDataTable.vue

Lines changed: 9 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -188,14 +188,15 @@
188188
</table>
189189

190190
<slot name="loading" v-if="loading">
191-
<div style="position:absolute;left:0;top:0;bottom:0;right:0;background-color:rgb(255,255,255,0.4);">
192-
<div style="position:absolute;bottom:50%;left:50%;transform:translateX(-50%);">
193-
<CSpinner color="success"/>
194-
</div>
195-
</div>
191+
<CElementCover
192+
:boundaries="[
193+
{ sides: ['top'], tag: 'TD' },
194+
{ sides: ['bottom'], tag: 'TBODY' }
195+
]"
196+
/>
196197
</slot>
197-
198198
</div>
199+
199200
<slot name="under-table"/>
200201

201202

@@ -210,7 +211,7 @@
210211
</template>
211212

212213
<script>
213-
import CSpinner from '../spinner/CSpinner'
214+
import CElementCover from '../element-cover/CElementCover'
214215
import CPagination from '../pagination/CPagination'
215216
import CIcon from '@coreui/icons-vue/src/CIconRaw.vue'
216217
import { cilArrowTop, cilBan } from '@coreui/icons'
@@ -220,7 +221,7 @@ export default {
220221
icons: { cilArrowTop, cilBan },
221222
components: {
222223
CPagination,
223-
CSpinner,
224+
CElementCover,
224225
CIcon
225226
},
226227
props: {

src/components/table/tests/CDataTable.spec.js

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -80,7 +80,7 @@ describe(ComponentName, () => {
8080
})
8181
it('shows loading layer when loading prop is set', () => {
8282
customWrapper.setProps({ loading: true })
83-
expect(customWrapper.contains('.spinner-border')).toBe(true)
83+
expect(customWrapper.contains('.spinner-grow')).toBe(true)
8484
customWrapper.setProps({ loading: false })
8585
})
8686
it('emits event when items per page changes', () => {

0 commit comments

Comments
 (0)