Skip to content

Commit 8c27214

Browse files
committed
feat(CPlaceholder): add new component and directive
1 parent 8c65225 commit 8c27214

File tree

12 files changed

+494
-10
lines changed

12 files changed

+494
-10
lines changed

packages/coreui-vue/src/components/index.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,7 @@ export * from './nav'
2424
export * from './navbar'
2525
export * from './offcanvas'
2626
export * from './pagination'
27+
export * from './placeholder'
2728
export * from './progress'
2829
export * from './popover'
2930
export * from './sidebar'
Lines changed: 139 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,139 @@
1+
import { defineComponent, h } from 'vue'
2+
3+
import { Color } from '../props'
4+
5+
const BREAKPOINTS = [
6+
'xxl' as const,
7+
'xl' as const,
8+
'lg' as const,
9+
'md' as const,
10+
'sm' as const,
11+
'xs' as const,
12+
]
13+
14+
export const CPlaceholder = defineComponent({
15+
name: 'CPlaceholder',
16+
props: {
17+
/**
18+
* Set animation type to better convey the perception of something being actively loaded.
19+
*
20+
* @values 'glow', 'wave'
21+
*/
22+
animation: {
23+
type: String,
24+
default: undefined,
25+
require: false,
26+
validator: (value: string) => {
27+
return ['glow', 'wave'].includes(value)
28+
},
29+
},
30+
/**
31+
* Sets the color context of the component to one of CoreUI’s themed colors.
32+
*
33+
* @values 'primary', 'secondary', 'success', 'danger', 'warning', 'info', 'dark', 'light'
34+
*/
35+
color: Color,
36+
/**
37+
* Component used for the root node. Either a string to use a HTML element or a component.
38+
*/
39+
component: {
40+
type: String,
41+
default: 'span',
42+
required: false,
43+
},
44+
/**
45+
* Size the component extra small, small, or large.
46+
*
47+
* @values 'xs', 'sm', 'lg'
48+
*/
49+
size: {
50+
type: String,
51+
default: undefined,
52+
required: false,
53+
validator: (value: string) => {
54+
return ['xs', 'sm', 'lg'].includes(value)
55+
},
56+
},
57+
/**
58+
* The number of columns on extra small devices (<576px).
59+
*/
60+
xs: {
61+
type: Number,
62+
default: undefined,
63+
require: false,
64+
},
65+
/**
66+
* The number of columns on small devices (<768px).
67+
*/
68+
sm: {
69+
type: Number,
70+
default: undefined,
71+
require: false,
72+
},
73+
/**
74+
* The number of columns on medium devices (<992px).
75+
*/
76+
md: {
77+
type: Number,
78+
default: undefined,
79+
require: false,
80+
},
81+
/**
82+
* The number of columns on large devices (<1200px).
83+
*/
84+
lg: {
85+
type: Number,
86+
default: undefined,
87+
require: false,
88+
},
89+
/**
90+
* The number of columns on X-Large devices (<1400px).
91+
*/
92+
xl: {
93+
type: Number,
94+
default: undefined,
95+
require: false,
96+
},
97+
/**
98+
* The number of columns on XX-Large devices (≥1400px).
99+
*/
100+
xxl: {
101+
type: Number,
102+
default: undefined,
103+
require: false,
104+
},
105+
},
106+
setup(props, { slots }) {
107+
const repsonsiveClassNames: string[] = []
108+
109+
BREAKPOINTS.forEach((bp) => {
110+
const breakpoint = props[bp]
111+
112+
const infix = bp === 'xs' ? '' : `-${bp}`
113+
114+
if (typeof breakpoint === 'number') {
115+
repsonsiveClassNames.push(`col${infix}-${breakpoint}`)
116+
}
117+
118+
if (typeof breakpoint === 'boolean') {
119+
repsonsiveClassNames.push(`col${infix}`)
120+
}
121+
})
122+
123+
return () =>
124+
h(
125+
props.component,
126+
{
127+
class: [
128+
props.animation ? `placeholder-${props.animation}` : 'placeholder',
129+
{
130+
[`bg-${props.color}`]: props.color,
131+
[`placeholder-${props.size}`]: props.size,
132+
},
133+
repsonsiveClassNames,
134+
],
135+
},
136+
slots.default && slots.default(),
137+
)
138+
},
139+
})
Lines changed: 44 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,44 @@
1+
import { shallowMount } from '@vue/test-utils'
2+
import { CPlaceholder as Component } from './../../'
3+
4+
const ComponentName = 'CPlaceholder'
5+
const wrapper = shallowMount(Component)
6+
const customWrapper = shallowMount(Component, {
7+
props: {
8+
animation: 'glow',
9+
color: 'secondary',
10+
size: 'lg',
11+
sm: 7,
12+
},
13+
attrs: {
14+
class: 'bazinga',
15+
},
16+
slots: {
17+
default: 'Hello World!',
18+
},
19+
})
20+
21+
describe(`Loads and display ${ComponentName} component`, () => {
22+
it('has a name', () => {
23+
expect(Component.name).toMatch(ComponentName)
24+
})
25+
it('renders correctly', () => {
26+
expect(wrapper.element).toMatchSnapshot()
27+
})
28+
it('renders correctly with slot', () => {
29+
expect(customWrapper.element).toMatchSnapshot()
30+
})
31+
})
32+
33+
describe(`Customize ${ComponentName} component`, () => {
34+
it('has a prope class names', () => {
35+
expect(customWrapper.find('span').classes('bazinga')).toBe(true)
36+
expect(customWrapper.find('span').classes('bg-secondary')).toBe(true)
37+
expect(customWrapper.find('span').classes('col-sm-7')).toBe(true)
38+
expect(customWrapper.find('span').classes('placeholder-glow')).toBe(true)
39+
expect(customWrapper.find('span').classes('placeholder-lg')).toBe(true)
40+
})
41+
it('default slot contains text', () => {
42+
expect(customWrapper.text()).toBe('Hello World!')
43+
})
44+
})
Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
// Jest Snapshot v1, https://goo.gl/fbAQLP
2+
3+
exports[`Loads and display CPlaceholder component renders correctly 1`] = `
4+
<span
5+
class="placeholder"
6+
/>
7+
`;
8+
9+
exports[`Loads and display CPlaceholder component renders correctly with slot 1`] = `
10+
<span
11+
class="placeholder-glow bg-secondary placeholder-lg col-sm-7 bazinga"
12+
>
13+
Hello World!
14+
</span>
15+
`;
Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
import { App } from 'vue'
2+
import { CPlaceholder } from './CPlaceholder'
3+
4+
const CPlaceholderPlugin = {
5+
install: (app: App): void => {
6+
app.component(CPlaceholder.name, CPlaceholder)
7+
},
8+
}
9+
10+
export { CPlaceholderPlugin, CPlaceholder }
Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
1-
import vctooltip from './v-c-tooltip'
1+
import vcplaceholder from './v-c-placeholder'
22
import vcpopover from './v-c-popover'
3+
import vctooltip from './v-c-tooltip'
34

4-
export { vctooltip, vcpopover }
5+
export { vcplaceholder, vcpopover, vctooltip, }
Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,32 @@
1+
import { DirectiveBinding } from 'vue'
2+
3+
const BREAKPOINTS = [
4+
'xxl' as const,
5+
'xl' as const,
6+
'lg' as const,
7+
'md' as const,
8+
'sm' as const,
9+
'xs' as const,
10+
]
11+
12+
export default {
13+
name: 'c-placeholder',
14+
mounted(el: HTMLElement, binding: DirectiveBinding): void {
15+
const value = binding.value
16+
el.classList.add(value.animation ? `placeholder-${value.animation}` : 'placeholder')
17+
18+
BREAKPOINTS.forEach((bp) => {
19+
const breakpoint = value[bp]
20+
21+
const infix = bp === 'xs' ? '' : `-${bp}`
22+
23+
if (typeof breakpoint === 'number') {
24+
el.classList.add(`col${infix}-${breakpoint}`)
25+
}
26+
27+
if (typeof breakpoint === 'boolean') {
28+
el.classList.add(`col${infix}`)
29+
}
30+
})
31+
},
32+
}
Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,33 @@
1+
import { ObjectDirective } from '@vue/runtime-core'
2+
3+
interface VShowElement extends HTMLElement {
4+
// _vod = vue original display
5+
_vod: string
6+
}
7+
8+
export const vVisible: ObjectDirective<VShowElement> = {
9+
beforeMount(el, { value }, { transition }) {
10+
el._vod = el.style.display === 'none' ? '' : el.style.display
11+
if (transition && value) {
12+
transition.beforeEnter(el)
13+
}
14+
},
15+
mounted(el, { value }, { transition }) {
16+
if (transition && value) {
17+
transition.enter(el)
18+
}
19+
},
20+
updated(el, { value, oldValue }, { transition }) {
21+
if (!value === !oldValue) return
22+
if (transition) {
23+
if (value) {
24+
transition.beforeEnter(el)
25+
transition.enter(el)
26+
} else {
27+
transition.leave(el, () => {
28+
// setDisplay(el, false)
29+
})
30+
}
31+
}
32+
},
33+
}

packages/coreui-vue/src/index.ts

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
//@ts-nocheck
22
import { App } from 'vue'
33
import * as Components from './components'
4-
import { vcpopover, vctooltip } from './directives'
4+
import { vcplaceholder, vcpopover, vctooltip } from './directives'
55

66
const removeKeysFromObject = (object, keys) => {
77
return Object.entries(object).reduce((obj, [key, value]) => {
@@ -30,6 +30,7 @@ const CoreuiVue = {
3030
// app.directive(directive, Directives[directive])
3131
// }
3232

33+
app.directive('c-placeholder', vcplaceholder)
3334
app.directive('c-popover', vcpopover)
3435
app.directive('c-tooltip', vctooltip)
3536
},

packages/docs/.vuepress/config.ts

Lines changed: 2 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -245,13 +245,8 @@ export default defineUserConfig<DefaultThemeOptions>({
245245
link: `/components/pagination.html`,
246246
},
247247
{
248-
text: 'Placeholders',
249-
link: `/components/placeholders.html`,
250-
disabled: true,
251-
badge: {
252-
color: 'warning',
253-
text: 'WIP v4.1',
254-
}
248+
text: 'Placeholder',
249+
link: `/components/placeholder.html`,
255250
},
256251
{
257252
text: 'Popover',

0 commit comments

Comments
 (0)