Add popper.js to dropdowns

This commit is contained in:
Cory LaViska
2020-01-08 00:02:03 -05:00
parent 6ca7338953
commit 7dbbf9baad
6 changed files with 67 additions and 20 deletions

5
package-lock.json generated
View File

@@ -452,6 +452,11 @@
"integrity": "sha512-GSmOT2EbHrINBf9SR7CDELwlJ8AENk3Qn7OikK4nFYAu3Ote2+JYNVvkpAEQm3/TLNEJFD/xZJjzyxg3KBWOzw==",
"dev": true
},
"popper.js": {
"version": "1.16.0",
"resolved": "https://registry.npmjs.org/popper.js/-/popper.js-1.16.0.tgz",
"integrity": "sha512-+G+EkOPoE5S/zChTpmBSSDYmhXJ5PsW8eMhH8cP/CQHMFPBG/kC9Y5IIw6qNYgdJ+/COf0ddY2li28iHaZRSjw=="
},
"pretty-bytes": {
"version": "5.3.0",
"resolved": "https://registry.npmjs.org/pretty-bytes/-/pretty-bytes-5.3.0.tgz",

View File

@@ -30,5 +30,7 @@
"workbox-build": "4.3.1"
},
"license": "MIT",
"dependencies": {}
"dependencies": {
"popper.js": "^1.16.0"
}
}

11
src/components.d.ts vendored
View File

@@ -55,6 +55,10 @@ export namespace Components {
interface SDropdown {
'close': () => Promise<void>;
'open': () => Promise<void>;
/**
* The preferred placement of the dropdown menu. Note that the actual placement may vary as needed to keep the menu inside of the viewport.
*/
'placement': 'bottom-start' | 'bottom-end' | 'top-start' | 'top-end';
}
interface SDropdownDivider {}
interface SDropdownItem {
@@ -354,7 +358,12 @@ declare namespace LocalJSX {
*/
'type'?: string;
}
interface SDropdown {}
interface SDropdown {
/**
* The preferred placement of the dropdown menu. Note that the actual placement may vary as needed to keep the menu inside of the viewport.
*/
'placement'?: 'bottom-start' | 'bottom-end' | 'top-start' | 'top-end';
}
interface SDropdownDivider {}
interface SDropdownItem {
/**

View File

@@ -18,11 +18,13 @@ $dropdown-color: $menu-color !default;
display: inline-block;
}
// TODO: use $dropdown-* variables here
.s-dropdown__trigger {
display: inline-block;
}
.s-dropdown__menu {
position: absolute;
top: calc(100% + 2px);
left: 0;
z-index: 9999;
font-family: $dropdown-font-family;
font-weight: $dropdown-font-weight;
font-size: $dropdown-font-size;
@@ -31,16 +33,12 @@ $dropdown-color: $menu-color !default;
border: solid 1px $dropdown-border-color;
border-radius: 4px;
box-shadow: $dropdown-box-shadow;
padding-top: 10px; // TODO: these should match $dropdown-item-padding-y
padding-bottom: 10px;
padding-top: $menu-padding-y;
padding-bottom: $menu-padding-y;
opacity: 0;
transform-origin: top left;
transform: scale(0.9);
transition: 100ms opacity, 100ms transform;
z-index: 10;
transition: 100ms opacity;
}
.s-dropdown--open .s-dropdown__menu {
opacity: 1;
transform: scale(1) translateY(0);
}

View File

@@ -1,4 +1,5 @@
import { Component, Method, State, h } from '@stencil/core';
import { Component, Method, Prop, State, Watch, h } from '@stencil/core';
import PopperJs from 'popper.js';
@Component({
tag: 's-dropdown',
@@ -8,6 +9,8 @@ import { Component, Method, State, h } from '@stencil/core';
export class Dropdown {
items: [HTMLSDropdownItemElement];
menu: HTMLElement;
popper: PopperJs;
trigger: HTMLElement;
constructor() {
this.handleDocumentClick = this.handleDocumentClick.bind(this);
@@ -20,10 +23,41 @@ export class Dropdown {
@State() isOpen = false;
/**
* The preferred placement of the dropdown menu. Note that the actual placement may vary as needed to keep the menu
* inside of the viewport.
*/
@Prop() placement: 'bottom-start' | 'bottom-end' | 'top-start' | 'top-end' = 'bottom-start';
@Watch('placement')
handlePlacementChange() {
if (this.popper) {
this.popper.options.placement = this.placement;
}
}
componentDidLoad() {
this.popper = new PopperJs(this.trigger, this.menu, {
placement: this.placement,
modifiers: {
offset: {
offset: '0, 2px'
}
}
});
}
componentDidUnload() {
if (this.popper) {
this.popper.destroy();
}
}
@Method()
async open() {
this.items = this.getAllItems();
this.menu.hidden = false;
this.popper.scheduleUpdate();
requestAnimationFrame(() => (this.isOpen = true));
document.addEventListener('click', this.handleDocumentClick);
@@ -70,8 +104,7 @@ export class Dropdown {
}
handleDocumentKeyDown(event: KeyboardEvent) {
if (event.key === 'Escape') {
event.preventDefault();
if (event.key === 'Escape' || event.key === 'Tab') {
this.close();
}
@@ -98,7 +131,7 @@ export class Dropdown {
}
handleMenuMouseDown(event: MouseEvent) {
// Keep focus on the dropdown trigger when selecting a menu item
// Keep focus on the dropdown trigger when selecting menu items
event.preventDefault();
}
@@ -135,7 +168,7 @@ export class Dropdown {
's-dropdown--open': this.isOpen
}}
>
<span class="s-dropdown__trigger" onClick={() => this.toggleMenu()}>
<span class="s-dropdown__trigger" ref={el => (this.trigger = el)} onClick={() => this.toggleMenu()}>
<slot name="trigger" />
</span>

View File

@@ -201,6 +201,9 @@
<s-dropdown-item>Dropdown Item 2</s-dropdown-item>
<s-dropdown-item>Dropdown Item 3</s-dropdown-item>
<s-dropdown-divider></s-dropdown-divider>
<s-dropdown-item checked>Checked</s-dropdown-item>
<s-dropdown-item disabled>Disabled</s-dropdown-item>
<s-dropdown-divider></s-dropdown-divider>
<s-dropdown-item>
Prefix
<span slot="prefix"><i class="fa fa-save"></i></span>
@@ -213,9 +216,6 @@
Suffix Icon
<span slot="suffix"><i class="fa fa-external-link"></i></span>
</s-dropdown-item>
<s-dropdown-divider></s-dropdown-divider>
<s-dropdown-item checked>Checked</s-dropdown-item>
<s-dropdown-item disabled>Disabled</s-dropdown-item>
</s-dropdown>
<hr />