Skip to content
This repository was archived by the owner on Mar 17, 2021. It is now read-only.

Commit abc43e6

Browse files
committed
Added popover component
1 parent 304e03f commit abc43e6

File tree

11 files changed

+671
-395
lines changed

11 files changed

+671
-395
lines changed

build/rollup.config.js

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -24,12 +24,14 @@ const external = [
2424
// eg. 'jquery'
2525
'fuzzysearch',
2626
'vue',
27+
'popper.js',
2728
];
2829
const globals = {
2930
// Provide global variable names to replace your external imports
3031
// eg. jquery: '$'
3132
fuzzysearch: 'fuzzysearch',
3233
vue: 'Vue',
34+
'popper.js': 'Popper',
3335
};
3436

3537
export default [

dist/modulist-vue.common.js

Lines changed: 1 addition & 1 deletion
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

dist/modulist-vue.esm.js

Lines changed: 1 addition & 1 deletion
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

dist/modulist-vue.min.js

Lines changed: 1 addition & 1 deletion
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

docs/.vuepress/config.js

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,7 @@ module.exports = {
2929
'components/modal',
3030
'components/notification',
3131
'components/pagination',
32+
'components/popover',
3233
'components/progress',
3334
'components/radio',
3435
'components/radio-group',

docs/components/popover.md

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,25 @@
1+
# Popover <badge text="development" type="warn" />
2+
Popover component used to show content in container, usually used in as part of dropdown.
3+
4+
## Example
5+
<div class="p-3 border rounded-2 my-3">
6+
<v-popover>
7+
<template v-slot:toggle>
8+
<v-button appearance="primary">Open popover</v-button>
9+
</template>
10+
<div>Default popover</div>
11+
</v-popover>
12+
</div>
13+
14+
## Props
15+
Name | Type | Description | Default | Required
16+
------------------ | --------- | ----------- | ------- | --------
17+
trigger | String | Popover can be opened on `click` or 'hover' event | 'click' | false
18+
containFocus | Boolean | Should focus stay inside popover or not | false | false
19+
hasMaxHeight | Boolean | Should popover be limited by height or not | true | false
20+
disabled | Boolean | Prevent opening popover | false | false
21+
placement | String | Placement of popover | 'bottom' | false
22+
offset | [String, Number] | Popover offset between trigger and container | '0' | false
23+
manualOpen | Boolean | Disable built logic to open/close popover. If `true` you have to implement your own logic | false | false
24+
manualClose | Boolean | Disable built logic to open/close popover. If `true` you have to implement your own logic | false | false
25+
returnFocusOnClose | Boolean | Trigger get back focus when popover close | true | false

package-lock.json

Lines changed: 435 additions & 388 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

package.json

Lines changed: 12 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,7 @@
2020
"prepublish": "bash scripts/prepublish.sh"
2121
},
2222
"devDependencies": {
23-
"@modulist/css": "0.0.29",
23+
"@modulist/css": "0.0.31",
2424
"@vue/cli-plugin-babel": "^3.5.0",
2525
"@vue/cli-plugin-eslint": "^3.5.0",
2626
"@vue/cli-plugin-unit-jest": "^3.5.0",
@@ -47,8 +47,15 @@
4747
"pre-commit": "lint-staged"
4848
},
4949
"lint-staged": {
50-
"*.{js,vue}": ["vue-cli-service lint", "git add"],
51-
"ignore": ["**/dist/*.js"]
50+
"linters": {
51+
"*.{js,vue}": [
52+
"vue-cli-service lint",
53+
"git add"
54+
]
55+
},
56+
"ignore": [
57+
"**/dist/*.js"
58+
]
5259
},
5360
"directories": {
5461
"doc": "docs",
@@ -65,6 +72,7 @@
6572
},
6673
"homepage": "https://github.com/simplystack/modulist-vue#readme",
6774
"dependencies": {
68-
"fuzzysearch": "^1.0.3"
75+
"fuzzysearch": "^1.0.3",
76+
"popper.js": "^1.14.7"
6977
}
7078
}

src/components/Popover/index.js

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
import Popover from './main.vue';
2+
3+
// eslint-disable-next-line func-names
4+
Popover.install = function (Vue) {
5+
Vue.component('VPopover', Popover);
6+
};
7+
8+
export default Popover;

src/components/Popover/main.vue

Lines changed: 184 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,184 @@
1+
<template>
2+
<div class="popover" @keydown.esc="close" :class="{'popover--has-max-height': hasMaxHeight}">
3+
<div class="popover__trigger" ref="trigger">
4+
<slot name="toggle"></slot>
5+
</div>
6+
<div
7+
ref="content"
8+
role="dialog"
9+
class="popover__content"
10+
aria-haspopup="true"
11+
:aria-expanded="isActive ? 'true' : 'false'"
12+
tabindex="-1"
13+
v-if="isActive"
14+
>
15+
<slot></slot>
16+
17+
<div class="popover__focus-redirector" tabindex="0" @focus="restrictFocus"></div>
18+
</div>
19+
</div>
20+
</template>
21+
22+
<script>
23+
import Popper from 'popper.js';
24+
25+
export default {
26+
name: 'VPopover',
27+
props: {
28+
trigger: {
29+
type: String,
30+
default: 'click',
31+
},
32+
containFocus: {
33+
type: Boolean,
34+
default: false,
35+
},
36+
hasMaxHeight: {
37+
type: Boolean,
38+
default: true,
39+
},
40+
disabled: {
41+
type: Boolean,
42+
default: false,
43+
},
44+
placement: {
45+
type: String,
46+
default: 'bottom',
47+
},
48+
offset: {
49+
type: [String, Number],
50+
default: '0',
51+
},
52+
manualOpen: {
53+
type: Boolean,
54+
default: false,
55+
},
56+
manualClose: {
57+
type: Boolean,
58+
default: false,
59+
},
60+
returnFocusOnClose: {
61+
type: Boolean,
62+
default: true,
63+
},
64+
focusRedirector: Function,
65+
},
66+
data() {
67+
return {
68+
isActive: false,
69+
popperInstance: null,
70+
focusedElBeforeOpen: null,
71+
};
72+
},
73+
mounted() {
74+
this.addEventsListeners();
75+
},
76+
computed: {
77+
triggerEl() {
78+
return this.$refs.trigger;
79+
},
80+
popperOptions() {
81+
return {
82+
placement: this.placement,
83+
modifiers: {
84+
offset: {
85+
offset: this.offset,
86+
},
87+
},
88+
};
89+
},
90+
},
91+
methods: {
92+
addEventsListeners() {
93+
switch (this.trigger) {
94+
case 'click':
95+
if (!this.manualClose) document.addEventListener('click', this.handleClickOutside, true);
96+
if (!this.manualOpen) this.triggerEl.addEventListener('click', this.toggle);
97+
break;
98+
case 'hover':
99+
if (!this.manualOpen) this.triggerEl.addEventListener('mouseenter', this.show);
100+
if (!this.manualClose) document.addEventListener('mousemove', this.handleClickOutside, true);
101+
break;
102+
default:
103+
throw new Error(`[popover] ${this.trigger} is not defined`);
104+
}
105+
},
106+
removeEventsListeners() {
107+
switch (this.trigger) {
108+
case 'click':
109+
if (!this.manualClose) document.removeEventListener('click', this.handleClickOutside, true);
110+
this.triggerEl.removeEventListener('click', this.toggle);
111+
break;
112+
case 'hover':
113+
if (!this.manualOpen) this.triggerEl.removeEventListener('mouseenter', this.show);
114+
if (!this.manualClose) document.removeEventListener('mousemove', this.handleClickOutside, true);
115+
break;
116+
default:
117+
throw new Error(`[popover] ${this.trigger} is not defined`);
118+
}
119+
},
120+
toggle() {
121+
if (this.isActive) {
122+
this.close();
123+
} else {
124+
this.show();
125+
}
126+
},
127+
show() {
128+
if (this.disabled) return;
129+
if (this.isActive) return;
130+
131+
this.focusedElBeforeOpen = document.activeElement;
132+
133+
this.isActive = true;
134+
this.initializePopper();
135+
this.$nextTick(() => {
136+
this.$el.focus();
137+
});
138+
this.$emit('open');
139+
},
140+
close() {
141+
this.isActive = false;
142+
this.$nextTick(() => {
143+
this.destroyPopper();
144+
});
145+
this.$emit('close');
146+
if (this.returnFocusOnClose) {
147+
this.triggerEl.focus();
148+
// this.focusedElBeforeOpen.focus();
149+
}
150+
},
151+
restrictFocus(e) {
152+
if (!this.containFocus) {
153+
this.close();
154+
return;
155+
}
156+
e.stopPropagation();
157+
if (this.focusRedirector) {
158+
this.focusRedirector(e);
159+
} else {
160+
this.$el.focus();
161+
}
162+
},
163+
initializePopper() {
164+
this.$nextTick(() => {
165+
this.popperInstance = new Popper(this.triggerEl, this.$refs.content, this.popperOptions);
166+
});
167+
},
168+
destroyPopper() {
169+
if (this.popperInstance) {
170+
this.popperInstance.destroy();
171+
this.popperInstance = null;
172+
}
173+
},
174+
handleClickOutside(e) {
175+
if (!this.$el.contains(e.target) && !this.triggerEl.contains(e.target) && this.isActive) {
176+
this.close();
177+
}
178+
},
179+
},
180+
beforeDestroy() {
181+
this.removeEventsListeners();
182+
},
183+
};
184+
</script>

0 commit comments

Comments
 (0)