Skip to content

Control the opening and closing of dialogs using HTML and vanilla JavaScript.

License

Notifications You must be signed in to change notification settings

ArthurClemens/dialogic-js

Repository files navigation

dialogic-js

npm

Control the opening and closing of dialogs, drawers and menus using HTML and (optionally) vanilla JavaScript.

This is a basic reimplementation of dialogic, to be used without a Virtual Dom library.

Installation

Including on a static site

<script src="https://unpkg.com/dialogic-js@0.3.1/dist/dialogic-js.min.js"></script>
<link href="https://unpkg.com/dialogic-js@0.3.1/dist/dialogic-js.min.css" rel="stylesheet" />

Installing via npm

npm install dialogic-js

Import the dependencies:

import { Prompt } from 'dialogic-js';
import 'dialogic-js/dialogic-js.css';

When using NextJS, use the JS import in client components.

Adding to a Phoenix LiveView project

Inside your assets folder, do:

npm install dialogic-js

Add to your app.js:

import { Prompt } from "dialogic-js";
import 'dialogic-js/dialogic-js.css';

Description

Prompt is a hook to control the opening and closing of dialogs and menus. It handles the showing and hiding of the HTML elements, without dealing with layout itself - to be implemented by you, or by using a UI library and adding "prompt" data attributes.

Features:

  • To be used in static HTML or templates
  • To be used with JavaScript
  • Easy to add to existing HTML markup from UI libraries
  • Add behaviours:
    • fading in and out
    • colored backdrop
    • closing on clicking background
    • modal (prevent closing on clicking background)
    • closing with the Escape key
    • focus first element
  • Callbacks
  • Support for stacked dialogs
  • Support for <details> and <dialog> elements
  • Support for Phoenix LiveView hooks

This library uses data attributes to supply to (new or existing) HTML markup. The supplementing CSS is defined around behaviours (state and modifiers), rather than visual appearance. The little styling provided can be customized with CSS Variables.

A basic example with <dialog> demonstrates how data attributes replace JS and add additional features.

BEFORE

<!-- HTML -->
<button onclick="showDialog('#my-dialog')">Open</button>

<dialog id="my-dialog">
  <p>Content</p>
  <button onclick="hideDialog('#my-dialog')">Close</button>
</dialog>
  
// JS
var dialog = document.querySelector('#my-dialog');

function showDialog(selector) {
  dialog.show();
}
function hideDialog(selector) {
  dialog.close();
}

AFTER - includes touch layer, backdrop, fade in and out

<!-- HTML -->
<div data-prompt>
  <button onclick="Prompt.show(this)">Open</button>
  <div data-backdrop></div>
  <div data-touch></div>
  <dialog data-content>
    <p>Content</p>
    <button onclick="Prompt.hide(this)">Close</button>
  </dialog>
</div>

Limitations

  • Support for menu behaviour is limited to appearing - no positioning and specific animations are implemented

Examples

Dialog

Menu

HTML structure

Both dialogs and menus share the same HTML structure. Dialogs are centered on the screen, whereas menus are normally positioned nearby the calling button (positioning not implemented by dialogic-js).

Dialogs

Singular dialog

HTML markup to create a single dialog:

<div data-prompt>
  <div data-backdrop></div>
  <div data-touch></div>
  <div data-content>
    Content
  </div>
</div>

Stacked dialogs

Online example:

If your application needs to show dialogs on top of other dialogs - perhaps in the case of a confirmation dialog that appears floating above the initial dialog, wrap element data-touch around the data-backdrop and data-content:

<div data-prompt>
  <div data-touch>
    <div data-backdrop></div>
    <div data-content>
      Content
    </div>
  </div>
</div>

Drawers

Global drawer

To create a screen size drawer, add data-isdrawer and data-drawer-content:

<div data-prompt data-isdrawer id="drawer">
  <div data-backdrop></div>
  <div data-touch></div>
  <div data-content>
    <div data-drawer-content>
      Drawer content
    </div>
  </div>
</div>

By default the drawer opens at the left side. To open at the right, pass data-isfarside.

<div data-prompt data-isdrawer data-isfarside id="drawer">
  <div data-backdrop></div>
  <div data-touch></div>
  <div data-content>
    <div data-drawer-content>
      Drawer content
    </div>
  </div>
</div>

The positions are automatically reversed for right-to-left languages when the drawer is inside an element with dir="rtl":

<div dir="rtl">
  <div data-prompt data-isdrawer data-isfarside id="drawer">
    <div data-backdrop></div>
    <div data-touch></div>
    <div data-content>
      <div data-drawer-content>
        Drawer content
      </div>
    </div>
  </div>
</div>

Local drawer (inside a container)

To create a local drawer, add data-islocal.

<div data-prompt data-isdrawer data-islocal id="drawer">
  <div class="some-container" style="overflow-x: hidden;">
    <div data-backdrop></div>
    <div data-touch></div>
    <div data-content>
      <div data-drawer-content>
        Drawer content
      </div>
    </div>
  </div>
</div>

Push drawer

A push drawer pushes the adjacent content aside when it opens. To create a push drawer, add data-ispush and wrap the backdrop and touch layers inside data-content:

<div data-prompt data-isdrawer data-ispush id="drawer">
  <div class="some-container">
    <div data-content>
      <div data-backdrop></div>
      <div data-touch></div>
      <div data-drawer-content>
        Drawer content
      </div>
      <div>
        Adjacent content
      </div>
    </div>
  </div>
</div>

Menus

Menus are supported with the same markup as dialogs, with this difference: add aria-role="menu" to the element that has data-content.

HTML markup to create menu behavior:

<div data-prompt>
  <div data-touch></div>
  <div data-content aria-role="menu">
    Content
  </div>
</div>

Other common settings used with menus:

  • Omit data-backdrop, or add data-islight to create a very light background
  • Add to data-prompt duration attribute data-isfast to get a fast transition

Example of HTML for a menu with all relevant (but some optional) attributes:

<div data-prompt data-isfast>
  <div data-backdrop data-islight></div>
  <div data-touch></div>
  <div data-content aria-role="menu">
    Content
  </div>
</div>

Data attributes

Common attributes

Data attribute Required Description
data-prompt required Container, may be a <details> element.
id - Modifier for data-prompt. When opening or closing from outside of this container, an id or other selector is required in order to call methods on it.
data-ismodal - Modifier for data-prompt. Creates modal behavior: content can't be closed by clicking on the background.
data-isescapable - Modifier for data-prompt. Closes the content when pressing the Escape key.
data-isfocusfirst - Modifier for data-prompt. On show, gives focus to the first focusable element (the first active element with the lowest tab index).
data-focusfirst="some-selector" - Modifier for data-prompt. Likewise, but find the focusable element by selector.
data-isfast - Modifier for data-prompt. Creates fast fade transitions for backdrop and content.
data-touch required Touch layer, detects clicks on background. For stacked dialogs, wrap this around the element data-content.
data-backdrop - Backdrop layer.
data-islight - Modifier for data-backdrop. Creates a light colored backdrop.
data-ismedium - (default) Modifier for data-backdrop. Creates a medium colored backdrop.
data-isdark - Modifier for data-backdrop. Creates a dark colored backdrop.
data-content required Content to be shown (a dialog or menu pane).
data-toggle - For buttons elements in situations when prompt.el has been assigned (using JavaScript).

Example of HTML with all relevant (but some optional) attributes:

<div data-prompt data-ismodal data-isescapable data-isfast>
  <div data-backdrop data-islight></div>
  <div data-touch></div>
  <div data-content>
    Content
  </div>
</div>

Extra attributes for drawers

Data attribute Required Description
data-isdrawer required Modifier for data-prompt. Creates drawer styles.
data-islocal - Modifier for data-prompt. Creates drawer styles for a local drawer.
data-ispush - Modifier for data-prompt. Creates drawer styles for a push drawer.
data-isfarside - Modifier for data-prompt. Opens the drawer at the far end of the reading direction.
data-drawer-content required Creates drawer content styles. Normally a modifier for data-content, except for push drawers.

Opening, closing and toggling

  • When using HTML markup: use methods on the Prompt object
  • When writing JavaAcript: create a new Prompt instance

HTML: Prompt methods

If you have a dialog container with this markup:

<div data-prompt>
  <button onclick="Prompt.show(this)">Show</button>
  <div data-backdrop></div>
  <div data-touch></div>
  <div data-content>
    Content
    <button onclick="Prompt.hide(this)">Hide</button>
  </div>
</div>

With React:

<div data-prompt id="my-prompt">
  <button onClick={() => Prompt.show('#my-prompt')}>Show</button>
  <div data-backdrop></div>
  <div data-touch></div>
  <div data-content>
    Content
    <button onClick={() => Prompt.hide('#my-prompt')}>Hide</button>
  </div>
</div>

When calling open from outside the prompt container, supply a selector:

<div data-prompt id="my-dialog">
  <div data-backdrop></div>
  <div data-touch></div>
  <div data-content>
    Content
    <button onclick="Prompt.hide(this)">Hide</button>
  </div>
</div>

<button onclick="Prompt.show('#my-dialog')">Show</button>

With React, use the React example with "onClick" above.

Opening, closing and toggling are done with these methods:

  • Prompt.show
  • Prompt.hide
  • Prompt.toggle
  • Prompt.init

Types used below

See dist/prompt.d.ts

Prompt.show

Prompt.show('#my-dialog');

Shows a closed dialog or menu.

show: (command: Command, options?: Options) => void;

Optionally pass options with callback functions:

{
  willShow: (elements: PromptElements) => void;
  didShow: (elements: PromptElements) => void;
}

Example:

<button
  onclick="Prompt.show('#my-dialog', { didShow: (elements) => console.log('showing', elements) })"
>
  Open dialog
</button>

With React:

<button
  onClick={() => Prompt.show('#my-dialog', { didShow: (elements) => console.log('showing', elements) })}
>
  Open dialog
</button>

Prompt.hide

Prompt.hide('#my-dialog');

Hides an open dialog or menu.

hide: (command: Command, options?: Options) => void;

Optionally pass options with callback functions:

{
  willHide: (elements: PromptElements) => void;
  didHide: (elements: PromptElements) => void;
}

Example:

<button
  onclick="Prompt.hide('#my-dialog', { didHide: (elements) => console.log('hidden', elements) })"
>
  Hide dialog
</button>

With React:

<button
  onClick={() => Prompt.hide('#my-dialog', { didHide: (elements) => console.log('hidden', elements) })}
>
  Hide dialog
</button>

Prompt.toggle

Prompt.toggle('#my-dialog');

Dependent on the current state: shows a closed dialog or menu or hides an open dialog or menu.

toggle: (command: Command, options?: Options) => void;

Optionally pass options with callback functions:

{
  willShow: (elements: PromptElements) => void;
  didShow: (elements: PromptElements) => void;
  willHide: (elements: PromptElements) => void;
  didHide: (elements: PromptElements) => void;
  getStatus: (status: PromptStatus) => void;
}

Prompt.init

Prompt.init('#my-dialog');

When used with <details>: initializes the prompt and shows the detail content

init: (command: Command) => void;

Example:

<details data-prompt ontoggle="Prompt.init(this)">

With React:

<details data-prompt id="my-details" onToggle={() => Prompt.init('my-details')}>

JavaScript: Methods on a Prompt instance

Online example:

Create an instance that can be passed around. Initialize it with attribute el.

<div data-prompt>
  <div data-backdrop></div>
  <div data-touch></div>
  <div data-content>
    Content
  </div>
</div>
import { Prompt } from 'dialogic-js';

const prompt = {...Prompt};
prompt.el = document.querySelector('#my-dialog')
prompt.show()
// some time later:
prompt.hide()

Support for the details element

Online examples:

The HTMLDetailsElement element can be used for toggling dialogs and menus. PrimerCSS contains a couple of neat examples, see for example PrimerCSS Dropdown. The summary element is styled as button - from the outside you'd never know the source is a details element.

The downside to using details is that it provides little support for transitions; the open state is on or off, which leads to a faily basic user experience.

With dialogic-js you can combine the details markup combined with transitions.

With HTML markup, the details is initialized with Prompt.init():

<details data-prompt ontoggle="Prompt.init(this)">
  <summary>Open</summary>
  <div data-backdrop></div>
  <div data-touch></div>
  <div data-content>
    Content
  </div>
</details>

With React:

<details data-prompt id="my-details" onToggle={() => Prompt.init('my-details')}>
   ...
</details>

The alternative approach, using a prompt instance:

<details data-prompt id="my-details">
  <summary>Open</summary>
  <div data-backdrop></div>
  <div data-touch></div>
  <div data-content>
    Content
  </div>
</details>
var prompt = {...Prompt};
prompt.el = document.querySelector("#my-details")
prompt.show()

Support for the dialog element

Online example: CodeSandbox with <dialog>

Add a wrapper around the <dialog> plus the required data attributes:

<div data-prompt id="my-dialog">
  <div data-touch></div>
  <dialog data-content>
    <p>Content in a minimal dialog</p>
    <button onclick="Prompt.hide(this)">Close</button>
  </dialog>
</div>

<button onclick="Prompt.show('#my-dialog')">Open</button>

With React, change the button tag to:

<button onClick={() => Prompt.hide('#my-dialog')}>Close</button>

When using a form:

<div data-prompt id="my-form-dialog" data-isescapable>
  <button onclick="Prompt.show(this)">
    Open form
  </button>
  <div data-touch></div>
  <div data-backdrop></div>
  <dialog data-content>
    <form method="dialog">
      <p>Would you like to continue?</p>
      <button type="submit" value="no" onclick="Prompt.hide(this)">No</button>
      <button type="submit" value="yes" onclick="Prompt.hide(this)">Yes</button>
    </form>
  </dialog>
</div>

With React, change onclick to onClick like the example above.

Improve dialog size and position with this CSS:

[data-prompt] dialog[data-content] {
  width: 100%;
  max-width: 60vw;
}

Not yet supported:

  • Calling dialog.close on closing the prompt

Custom styles

CSS Variables

Styles are defined by CSS variables. Override the default values to your own requirements. For example:

<style>
  [data-prompt] {
    --prompt-background-color-backdrop: gray;
    --prompt-transition-duration-content: 350ms;
  }
</style>

Default values:

[data-prompt] {
  /* colors */
  --prompt-background-color-backdrop: black;
  --prompt-background-opacity-backdrop-dark: 0.5;
  /* - default: */
  --prompt-background-opacity-backdrop-medium: 0.2;
  --prompt-background-opacity-backdrop-light: 0.07;
  /* transitions */
  --prompt-transition-timing-function-backdrop: ease-in-out;
  --prompt-transition-timing-function-content: ease-in-out;
  --prompt-transition-duration-content: 200ms;
  --prompt-fast-transition-duration-content: 150ms;
  --prompt-transition-duration-backdrop: var(--prompt-transition-duration-content);
  --prompt-fast-transition-duration-backdrop: var(--prompt-fast-transition-duration-content);
  /* drawer */
  --drawer-width: 320px;
  /* z-index */
  --prompt-z-index-backdrop: 98;
  --prompt-z-index-touch: 99;
  --prompt-z-index-content: 100;
}

Conditional CSS

You can use @import with media queries if you need to use dialogic CSS at specific screen sizes:

/* app.css */

@import url("./dialogic-js.css") only screen and (min-width: 544px);

Sizes

dist/dialogic-js.js   11.3kb
dist/dialogic-js.css   7.2kb

dist/dialogic-js.cjs  12.1kb
dist/dialogic-js.css   7.2kb

dist/dialogic-js.min.css  6.4kb
dist/dialogic-js.min.js   4.9kb

About

Control the opening and closing of dialogs using HTML and vanilla JavaScript.

Resources

License

Stars

Watchers

Forks

Packages

No packages published