Skip to content

Commit 99942a6

Browse files
committed
feat(CPopover): add new component
1 parent 63589c7 commit 99942a6

File tree

6 files changed

+313
-7
lines changed

6 files changed

+313
-7
lines changed

docs/api/popover/CPopover.api.md

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,25 @@
1+
### CPopover
2+
3+
```jsx
4+
import { CPopover } from '@coreui/vue'
5+
// or
6+
import CPopover from '@coreui/vue/src/components/popover/CPopover'
7+
```
8+
9+
#### Props
10+
11+
| Prop name | Description | Type | Values | Default |
12+
| ------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ------------------ | ------------------------------- | ------- |
13+
| **content** | Content for your component. If you want to pass non-string value please use dedicated slot `<template #content>...</template>` | string | - | - |
14+
| **offset** | Offset of the popover relative to its target. | array | - | [0, 8] |
15+
| **placement** | Describes the placement of your component after Popper.js has applied all the modifiers that may have flipped or altered the originally provided placement property. | Placement | - | 'top' |
16+
| **title** | Title for your component. If you want to pass non-string value please use dedicated slot `<template #title>...</template>` | string | - | - |
17+
| **trigger** | Sets which event handlers you’d like provided to your toggle prop. You can specify one trigger or an array of them. | string \| string[] | `'click'`, `'focus'`, `'hover'` | 'click' |
18+
| **visible** | Toggle the visibility of popover component. | boolean | - | |
19+
20+
#### Events
21+
22+
| Event name | Description | Properties |
23+
| ---------- | -------------------------------------------------------- | ---------- |
24+
| **hide** | Callback fired when the component requests to be hidden. |
25+
| **show** | Callback fired when the component requests to be shown. |

docs/components/popover.md

Lines changed: 72 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,30 @@
11
---
2-
title: Vue Popover Directive
2+
title: Vue Popover Component and Directive
33
name: Popover
44
description: Documentation and examples for adding Vue popovers, like those found in iOS, to any element on your site.
55
---
66

77
## Example
88

9+
### Component
10+
11+
::: demo
12+
<CPopover title="Popover title" content="And here\’s some amazing content. It’s very engaging. Right?" placement="right">
13+
<template #toggler="{ on }">
14+
<CButton color="danger" size="lg" v-on="on">Click to toggle popover</CButton>
15+
</template>
16+
</CPopover>
17+
:::
18+
```vue
19+
<CPopover title="Popover title" content="And here\’s some amazing content. It’s very engaging. Right?" placement="right">
20+
<template #toggler="{ on }">
21+
<CButton color="danger" size="lg" v-on="on">Click to toggle popover</CButton>
22+
</template>
23+
</CPopover>
24+
```
25+
26+
### Directive
27+
928
::: demo
1029
<CButton color="danger" size="lg" v-c-popover="{header: 'Popover title', content: 'And here\’s some amazing content. It’s very engaging. Right?', placement: 'right'}">Click to toggle popover</CButton>
1130
:::
@@ -17,6 +36,55 @@ description: Documentation and examples for adding Vue popovers, like those foun
1736

1837
Four options are available: top, right, bottom, and left aligned. Directions are mirrored when using CoreUI for Vue in RTL.
1938

39+
#### Component
40+
41+
::: demo
42+
<CPopover content="Vivamus sagittis lacus vel augue laoreet rutrum faucibus" placement="top">
43+
<template #toggler="{ on }">
44+
<CButton color="secondary" v-on="on">Popover on top</CButton>
45+
</template>
46+
</CPopover>
47+
<CPopover content="Vivamus sagittis lacus vel augue laoreet rutrum faucibus" placement="right">
48+
<template #toggler="{ on }">
49+
<CButton color="secondary" v-on="on">Popover on right</CButton>
50+
</template>
51+
</CPopover>
52+
<CPopover content="Vivamus sagittis lacus vel augue laoreet rutrum faucibus" placement="bottom">
53+
<template #toggler="{ on }">
54+
<CButton color="secondary" v-on="on">Popover on bottom</CButton>
55+
</template>
56+
</CPopover>
57+
<CPopover content="Vivamus sagittis lacus vel augue laoreet rutrum faucibus" placement="left">
58+
<template #toggler="{ on }">
59+
<CButton color="secondary" v-on="on">Popover on left</CButton>
60+
</template>
61+
</CPopover>
62+
:::
63+
```vue
64+
<CPopover content="Vivamus sagittis lacus vel augue laoreet rutrum faucibus" placement="top">
65+
<template #toggler="{ on }">
66+
<CButton color="secondary" v-on="on">Popover on top</CButton>
67+
</template>
68+
</CPopover>
69+
<CPopover content="Vivamus sagittis lacus vel augue laoreet rutrum faucibus" placement="right">
70+
<template #toggler="{ on }">
71+
<CButton color="secondary" v-on="on">Popover on right</CButton>
72+
</template>
73+
</CPopover>
74+
<CPopover content="Vivamus sagittis lacus vel augue laoreet rutrum faucibus" placement="bottom">
75+
<template #toggler="{ on }">
76+
<CButton color="secondary" v-on="on">Popover on bottom</CButton>
77+
</template>
78+
</CPopover>
79+
<CPopover content="Vivamus sagittis lacus vel augue laoreet rutrum faucibus" placement="left">
80+
<template #toggler="{ on }">
81+
<CButton color="secondary" v-on="on">Popover on left</CButton>
82+
</template>
83+
</CPopover>
84+
```
85+
86+
#### Directive
87+
2088
::: demo
2189
<CButton color="secondary" v-c-popover="{content: 'Vivamus sagittis lacus vel augue laoreet rutrum faucibus.', placement: 'top'}">Popover on top</CButton>
2290
<CButton color="secondary" v-c-popover="{content: 'Vivamus sagittis lacus vel augue laoreet rutrum faucibus.', placement: 'right'}">Popover on right</CButton>
@@ -30,3 +98,6 @@ Four options are available: top, right, bottom, and left aligned. Directions are
3098
<CButton color="secondary" v-c-popover="{content: 'Vivamus sagittis lacus vel augue laoreet rutrum faucibus.', placement: 'left'}">Popover on left</CButton>
3199
```
32100

101+
## API
102+
103+
!!!include(./docs/api/popover/CPopover.api.md)!!!

src/components/index.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,7 @@ export * from './navbar'
2525
export * from './offcanvas'
2626
export * from './pagination'
2727
export * from './progress'
28+
export * from './popover'
2829
export * from './sidebar'
2930
export * from './spinner'
3031
export * from './table'

src/components/popover/CPopover.ts

Lines changed: 196 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,196 @@
1+
import { defineComponent, h, PropType, ref, RendererElement, Teleport, Transition } from 'vue'
2+
import { createPopper, Placement } from '@popperjs/core'
3+
4+
const CPopover = defineComponent({
5+
name: 'CPopover',
6+
props: {
7+
/**
8+
* Content for your component. If you want to pass non-string value please use dedicated slot `<template #content>...</template>`
9+
*/
10+
content: {
11+
type: String,
12+
default: undefined,
13+
required: false,
14+
},
15+
/**
16+
* Offset of the popover relative to its target.
17+
*/
18+
offset: {
19+
type: Array,
20+
default: () => [0, 8],
21+
required: false,
22+
},
23+
/**
24+
* Describes the placement of your component after Popper.js has applied all the modifiers that may have flipped or altered the originally provided placement property.
25+
*/
26+
placement: {
27+
type: String as PropType<Placement>,
28+
default: 'top',
29+
required: false,
30+
validator: (value: string) => {
31+
return ['top', 'right', 'bottom', 'left'].includes(value)
32+
},
33+
},
34+
/**
35+
* Title for your component. If you want to pass non-string value please use dedicated slot `<template #title>...</template>`
36+
*/
37+
title: {
38+
type: String,
39+
default: undefined,
40+
required: false,
41+
},
42+
/**
43+
* Sets which event handlers you’d like provided to your toggle prop. You can specify one trigger or an array of them.
44+
*
45+
* @values 'click', 'focus', 'hover'
46+
*/
47+
trigger: {
48+
type: [String, Array] as PropType<string | string[]>,
49+
default: 'click',
50+
required: false,
51+
validator: (value: string | string[]) => {
52+
if (typeof value === 'string') {
53+
return ['click', 'focus', 'hover'].includes(value)
54+
}
55+
if (Array.isArray(value)) {
56+
return value.every((e) => ['click', 'focus', 'hover'].includes(e))
57+
}
58+
return false
59+
},
60+
},
61+
/**
62+
* Toggle the visibility of popover component.
63+
*/
64+
visible: Boolean,
65+
},
66+
emits: [
67+
/**
68+
* Callback fired when the component requests to be hidden.
69+
*/
70+
'hide',
71+
/**
72+
* Callback fired when the component requests to be shown.
73+
*/
74+
'show',
75+
],
76+
setup(props, { slots, emit }) {
77+
const togglerRef = ref()
78+
const popoverRef = ref()
79+
const popper = ref()
80+
const visible = ref(props.visible)
81+
82+
const handleEnter = (el: RendererElement, done: () => void) => {
83+
emit('show')
84+
initPopper()
85+
el.classList.add('show')
86+
el.addEventListener('transitionend', () => {
87+
done()
88+
})
89+
}
90+
91+
const handleLeave = (el: RendererElement, done: () => void) => {
92+
emit('hide')
93+
el.classList.remove('show')
94+
el.addEventListener('transitionend', () => {
95+
done()
96+
destroyPopper()
97+
})
98+
}
99+
100+
const handleToggle = () => {
101+
visible.value = !visible.value
102+
}
103+
104+
const initPopper = () => {
105+
if (togglerRef.value) {
106+
popper.value = createPopper(togglerRef.value.firstChild, popoverRef.value, {
107+
placement: props.placement,
108+
modifiers: [
109+
{
110+
name: 'offset',
111+
options: {
112+
offset: props.offset,
113+
},
114+
},
115+
],
116+
})
117+
}
118+
}
119+
120+
const destroyPopper = () => {
121+
if (popper.value) {
122+
popper.value.destroy()
123+
}
124+
popper.value = undefined
125+
}
126+
127+
return () => [
128+
h(
129+
Teleport,
130+
{
131+
to: 'body',
132+
},
133+
h(
134+
Transition,
135+
{
136+
onEnter: (el, done) => handleEnter(el, done),
137+
onLeave: (el, done) => handleLeave(el, done),
138+
},
139+
() =>
140+
visible.value &&
141+
h(
142+
'div',
143+
{
144+
class: 'popover fade bs-popover-auto',
145+
ref: popoverRef,
146+
role: 'tooltip',
147+
},
148+
[
149+
h('div', { class: 'popover-arrow', 'data-popper-arrow': '' }),
150+
(props.title || slots.title) &&
151+
h(
152+
'div',
153+
{ class: 'popover-header' },
154+
{
155+
default: () => (slots.title && slots.title()) || props.title,
156+
},
157+
),
158+
(props.content || slots.content) &&
159+
h(
160+
'div',
161+
{ class: 'popover-body' },
162+
{
163+
default: () => (slots.content && slots.content()) || props.content,
164+
},
165+
),
166+
],
167+
),
168+
),
169+
),
170+
h(
171+
'span',
172+
{
173+
style: {
174+
display: 'contents',
175+
},
176+
ref: togglerRef,
177+
},
178+
{
179+
default: () =>
180+
slots.toggler &&
181+
slots.toggler({
182+
on: {
183+
click: () => props.trigger.includes('click') && handleToggle(),
184+
blur: () => props.trigger.includes('focus') && handleToggle(),
185+
focus: () => props.trigger.includes('focus') && handleToggle(),
186+
mouseenter: () => props.trigger.includes('hover') && handleToggle(),
187+
mouseleave: () => props.trigger.includes('hover') && handleToggle(),
188+
},
189+
}),
190+
},
191+
),
192+
]
193+
},
194+
})
195+
196+
export { CPopover }

src/components/popover/index.ts

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 { CPopover } from './CPopover'
3+
4+
const CPopoverPlugin = {
5+
install: (app: App): void => {
6+
app.component(CPopover.name, CPopover)
7+
},
8+
}
9+
10+
export { CPopoverPlugin, CPopover }

src/index.ts

Lines changed: 9 additions & 6 deletions
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 * as Directives from './directives'
4+
import { vcpopover, vctooltip } from './directives'
55

66
const removeKeysFromObject = (object, keys) => {
77
return Object.entries(object).reduce((obj, [key, value]) => {
@@ -15,20 +15,23 @@ const removeKeysFromObject = (object, keys) => {
1515
const CoreuiVue = {
1616
install: (app: App, options: any): void => {
1717
let pluginComponents = Components
18-
let pluginDirectives = Directives
18+
// let pluginDirectives = Directives
1919

2020
const toRemove = options && options.remove ? options.remove : null
2121
if (toRemove && Array.isArray(toRemove)) {
2222
pluginComponents = removeKeysFromObject(Components, toRemove)
23-
pluginDirectives = removeKeysFromObject(Directives, toRemove)
23+
// pluginDirectives = removeKeysFromObject(Directives, toRemove)
2424
}
2525

2626
for (const plugin in pluginComponents) {
2727
app.component(plugin, Components[plugin])
2828
}
29-
for (const directive in pluginDirectives) {
30-
app.directive(directive, Directives[directive])
31-
}
29+
// for (const directive in pluginDirectives) {
30+
// app.directive(directive, Directives[directive])
31+
// }
32+
33+
app.directive('c-popover', vcpopover)
34+
app.directive('c-tooltip', vctooltip)
3235
},
3336
}
3437

0 commit comments

Comments
 (0)