Skip to content

Commit

Permalink
Add support for an overlayPosition property which supports values o…
Browse files Browse the repository at this point in the history
…f `'parent'` or `'sibling'`

- this property affects the relative DOM elements for non-tethered use cases
- `parent`, the default, uses a structure of:

```
   wrapper
     > overlay
       > container
```

- `sibling`, uses a structure of: `wrapper > overlay > container`

```
   wrapper
     > overlay
     > container
```
  • Loading branch information
lukemelia committed May 14, 2017
1 parent 28b051e commit 615a2ca
Show file tree
Hide file tree
Showing 7 changed files with 117 additions and 34 deletions.
25 changes: 13 additions & 12 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -85,6 +85,7 @@ Property | Purpose
`onClickOverlay` | An action to be called when the overlay is clicked. If this action is specified, clicking the overlay will invoke it instead of `onClose`.
`clickOutsideToClose` | Indicates whether clicking outside a modal *without* an overlay should close the modal. Useful if your modal isn't the focus of interaction, and you want hover effects to still work outside the modal.
`renderInPlace` | A boolean, when true renders the modal without wormholing or tethering, useful for including a modal in a style guide
`overlayPosition` | either `'parent'` or `'sibling'`, to control whether the overlay div is rendered as a parent element of the container div or as a sibling to it (default: `'parent'`)
`containerClass` | CSS class name(s) to append to container divs. Set this from template.
`containerClassNames` | CSS class names to append to container divs. This is a concatenated property, so it does **not** replace the default container class (default: `'ember-modal-dialog'`. If you subclass this component, you may define this in your subclass.)
`overlayClass` | CSS class name(s) to append to overlay divs. Set this from template.
Expand Down Expand Up @@ -120,44 +121,44 @@ Property | Purpose

## Which Component Should I Use?

Various modal use cases are best supported by different DOM structures. Ember Modal Dialog provides the following components:
Various modal use cases are best supported by different DOM structures. Ember Modal Dialog's `modal-dialog` component provides the following capabilities:

- modal-dialog: Uses ember-wormhole to append the following nested divs to the destination element: wrapper div > overlay div > container div
- modal-dialog without passing a `tetherTarget`: Uses ember-wormhole to append the following parent divs to the destination element: wrapper div > overlay div > container div

![](tests/dummy/public/modal-dialog.png)

- tether-dialog: Uses ember-tether to display modal container div. Uses ember-wormhole to append optional overlay div to the destination element. Requires separate installation of [ember-tether](//github.com/yapplabs/ember-tether) dependency.
This can be customized (see `overlayPosition`).

- modal-dialog, with a `tetherTarget` provided: Uses ember-tether to display modal container div. Uses ember-wormhole to append optional overlay div to the destination element. Requires separate installation of [ember-tether](//github.com/yapplabs/ember-tether) dependency.

![](tests/dummy/public/tether-dialog.png)

## Positioning

Ember Modal Dialog provides `attachment` and `targetAttachment` properties to configure positioning of the modal dialog near its target. To provide consistency with Hubspot Tether, Ember Modal Dialog uses the same syntax for these properties: "top|middle|bottom left|center|right|elementCenter"... e.g. `'middle left'`

### Positioning Your Modal Dialog

With the default SCSS provided, your modal will be centered in the viewport. By adjusting the CSS, you can adjust this logic.

Pass a `tetherTarget` in order to position our modal in relation to the target and enable your modal remain positioned near their targets when users scroll or resize the window.

Use `attachment` and `targetAttachment` properties to configure positioning of the modal dialog near its target. Ember Modal Dialog uses the syntax from Hubspot Tether for these properties: "top|middle|bottom left|center|right|elementCenter"... e.g. `'middle left'`

To enable this behavior, install ember-tether as a dependency of **your ember app**.

`ember install ember-tether`

Then use the tether-dialog component for any modals you wish to position this way:
Then pass a selector as `tetherTarget` for the modal you wish to position this way:

```htmlbars
{{#tether-dialog
target='#target-element-id'
{{#modal-dialog
tetherTarget='#target-element-id'
targetAttachment='middle right'
attachment='middle left'}}
I am a modal that will remain tethered to the right of the element with id 'target-element-id'
{{/tether-dialog}}
{{/modal-dialog}}
```

#### Caveats

Event delegation originating from content inside ember-tether blocks will only work for Ember apps that use Ember's default root element of the `body` tag. This is because the Hubspot Tether library appends its positioned elements to the body tag.
Event delegation originating from content inside ember-tether blocks will only work for Ember apps that use Ember's default root element of the `body` tag. This is because, generally speaking, the Hubspot Tether library appends its positioned elements to the body element.

If you are not overriding the default root element, then don't worry and carry on. ember-tether will work just fine for you.

Expand Down
6 changes: 3 additions & 3 deletions addon/components/basic-dialog.js
Original file line number Diff line number Diff line change
Expand Up @@ -42,9 +42,9 @@ export default Ember.Component.extend({
clickOutsideToClose: false,
hasOverlay: true,
isCentered: true,
overlayDOMPosition: null,
isOverlaySibling: computed('overlayDOMPosition', function() {
return this.get('overlayDOMPosition') === 'sibling';
overlayPosition: null,
isOverlaySibling: computed('overlayPosition', function() {
return this.get('overlayPosition') === 'sibling';
}),

makeOverlayClickableOnIOS: Ember.on('didInsertElement', function() {
Expand Down
23 changes: 21 additions & 2 deletions addon/components/modal-dialog.js
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,10 @@ import Ember from 'ember';
import layout from '../templates/components/modal-dialog';
const { computed, inject, isEmpty } = Ember;
const { dasherize } = Ember.String;
import { deprecate } from '@ember/debug';
import { deprecate, warn } from '@ember/debug';
import { DEBUG } from '@glimmer/env';

const VALID_OVERLAY_POSITIONS = ['parent', 'sibling'];

export default Ember.Component.extend({
tagName: '',
Expand All @@ -20,7 +23,22 @@ export default Ember.Component.extend({
}
return 'ember-modal-dialog/-basic-dialog';
}),

didReceiveAttrs() {
this._super(...arguments);
if (DEBUG) {
this.validateProps();
}
},
validateProps() {
let overlayPosition = this.get('overlayPosition');
if (VALID_OVERLAY_POSITIONS.indexOf(overlayPosition) === -1) {
warn(
`overlayPosition value '${overlayPosition}' is not valid (valid values [${VALID_OVERLAY_POSITIONS.join(', ')}])`,
false,
{ id: 'ember-modal-dialog.validate-overlay-position'}
);
}
},
// onClose - set this from templates
close: computed('onClose', {
get() {
Expand Down Expand Up @@ -88,6 +106,7 @@ export default Ember.Component.extend({

hasOverlay: true,
translucentOverlay: false,
overlayPosition: 'parent', // `parent` or `sibling`
clickOutsideToClose: false,
renderInPlace: false,
tetherTarget: null,
Expand Down
43 changes: 29 additions & 14 deletions addon/templates/components/basic-dialog.hbs
Original file line number Diff line number Diff line change
@@ -1,23 +1,38 @@
{{#ember-wormhole to=destinationElementId}}
<div class="{{wrapperClassNamesString}} {{wrapperClass}}">
{{#if hasOverlay}}
<div class={{overlayClassNamesString}} onclick={{action (ignore-children onClickOverlay)}} tabindex="-1" data-emd-overlay>
{{#ember-modal-dialog-positioned-container
class=containerClassNamesString
targetAttachment=targetAttachment
target=legacyTarget
}}
{{yield}}
{{/ember-modal-dialog-positioned-container}}
</div>
{{else}}
{{#if isOverlaySibling}}
<div class="{{wrapperClassNamesString}} {{wrapperClass}}">
{{#if hasOverlay}}
<div class={{overlayClassNamesString}} onclick={{action onClickOverlay}} tabindex="-1" data-emd-overlay></div>
{{/if}}
{{#ember-modal-dialog-positioned-container
class=containerClassNamesString
targetAttachment=targetAttachment
target=legacyTarget
}}
{{yield}}
{{/ember-modal-dialog-positioned-container}}
{{/if}}
</div>
</div>
{{else}}
<div class="{{wrapperClassNamesString}} {{wrapperClass}}">
{{#if hasOverlay}}
<div class={{overlayClassNamesString}} onclick={{action (ignore-children onClickOverlay)}} tabindex="-1" data-emd-overlay>
{{#ember-modal-dialog-positioned-container
class=containerClassNamesString
targetAttachment=targetAttachment
target=legacyTarget
}}
{{yield}}
{{/ember-modal-dialog-positioned-container}}
</div>
{{else}}
{{#ember-modal-dialog-positioned-container
class=containerClassNamesString
targetAttachment=targetAttachment
target=legacyTarget
}}
{{yield}}
{{/ember-modal-dialog-positioned-container}}
{{/if}}
</div>
{{/if}}
{{/ember-wormhole}}
1 change: 1 addition & 0 deletions addon/templates/components/modal-dialog.hbs
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@
translucentOverlay=translucentOverlay
clickOutsideToClose=clickOutsideToClose
destinationElementId=destinationElementId
overlayPosition=overlayPosition

tetherTarget=tetherTarget
legacyTarget=target
Expand Down
28 changes: 28 additions & 0 deletions tests/acceptance/basic-test.js
Original file line number Diff line number Diff line change
Expand Up @@ -67,6 +67,34 @@ test('modal without overlay', async function(assert) {
});
});

test('modal with overlay', async function(assert) {
await assert.dialogOpensAndCloses({
openSelector: '#example-translucent button',
dialogText: 'With Translucent Overlay',
closeSelector: overlaySelector
});

await assert.dialogOpensAndCloses({
openSelector: '#example-translucent button',
dialogText: 'With Translucent Overlay',
closeSelector: dialogCloseButton
});
});

test('modal with sibling overlay', async function(assert) {
await assert.dialogOpensAndCloses({
openSelector: '#example-overlay-sibling button',
dialogText: 'With Translucent Overlay as Sibling',
closeSelector: overlaySelector
});

await assert.dialogOpensAndCloses({
openSelector: '#example-overlay-sibling button',
dialogText: 'With Translucent Overlay as Sibling',
closeSelector: dialogCloseButton
});
});

test('clicking translucent overlay triggers callback', async function(assert) {
window.onClickOverlayCallbackCalled = false;

Expand Down
25 changes: 22 additions & 3 deletions tests/dummy/app/templates/index.hbs
Original file line number Diff line number Diff line change
Expand Up @@ -81,6 +81,25 @@
{{/if}}
</div>

<div class='example' id='example-overlay-sibling'>
<h2>With Overlay as a Sibling in the DOM</h2>
<button onclick={{action (mut isShowingSibling) true}}>Do It</button>
{{code-snippet name='translucent-modal-dialog-sibling.hbs'}}
{{#if isShowingSibling}}
{{!-- BEGIN-SNIPPET translucent-modal-dialog-sibling --}}
{{#modal-dialog
onClose=(action (mut isShowingSibling) false)
translucentOverlay=true
overlayPosition='sibling'
}}
<h1>Stop! Modal Time!</h1>
<p>With Translucent Overlay as Sibling</p>
<button onclick={{action (mut isShowingSibling) false}}>Close</button>
{{/modal-dialog}}
{{!-- END-SNIPPET --}}
{{/if}}
</div>

<div class='example' id='example-custom-styles'>
<h2>Custom Styles</h2>
<button onclick={{action (mut isShowingCustomStyles) true}}>Do It</button>
Expand Down Expand Up @@ -204,9 +223,9 @@
onClose='toggleCenteredScrolling'
translucentOverlay=true
targetAttachment='none'
container-class='centered-scrolling-container'
overlay-class='centered-scrolling-overlay'
wrapper-class='centered-scrolling-wrapper'
containerClass='centered-scrolling-container'
overlayClass='centered-scrolling-overlay'
wrapperClass='centered-scrolling-wrapper'
}}
<h1>Really Long Content To Demonstrate Scrolling</h1>
<ul>
Expand Down

0 comments on commit 615a2ca

Please sign in to comment.