Modal
‹ BackModals are implemented using a mixture of react-modal, custom react components and some custom binding wrappers.
Basic Modal
<button data-modal="basic" class="button">Open modal</button> <%= react_component "Modal", props: { id: "basic", label: "Sample modal", renderHTML: modal_content, } %>
Available component options
id | Event name for use with Ornament.openModal() |
label | Used in the header and for screenreaders |
isOpen | Boolean to determine wether modal is open by default |
render | Render prop when used in react, expects a react component |
renderHTML | HTML to be rendered inside modal |
noHeader | Boolean to disable the header |
className | Custom class for the wrapper element |
afterClose | Function to call after closing modal |
afterOpen | Function to call after opening modal |
Events
Events can be listened for with document.addEventListener(eventName, func)
.
- ornament:modal:{id}:before-open
- ornament:modal:{id}:after-open
- ornament:modal:{id}:before-close
- ornament:modal:{id}:after-close
You can trigger events in the modal by calling events on the document, eg. Ornament.fireEvent(eventName)
.
- ornament:modal:{id}:open
- ornament:modal:{id}:close
Sizing
Using the size
prop, you can change the size of your modal.
Sizes are defined entirely in CSS, check out the modal.scss
partial to configure your sizes.
Ornament by default comes with these sizes:
<button data-modal="size-small" class="button">Open small modal</button> <%= react_component "Modal", props: { id: "size-small", label: "Small modal", size: "small", renderHTML: modal_content, } %>
<%button data-modal="size-large" class="button">Open large modal<%/button> <%%= react_component "Modal", props: { id: "size-large", label: "Large modal", size: "large", renderHTML: modal_content, } %>
<button data-modal="size-fullscreen" class="button">Open fullscreen modal</button> <%= react_component "Modal", props: { id: "size-fullscreen", label: "Fullscreen modal", size: "fullscreen", renderHTML: modal_content, } %>
All modals with the exception of videos and ajax loading/error states will go fullscreen when they hit a breakpoint.
This behaviour can be disabled globally by setting the sass variable $fullscreen-mobile
to false.
This behaviour can also be disabled as a prop with noFullscreen: true
.
Ajax content
A common pattern is to load in an ajax endpoint and inject the resulting HTML in to a modal. This can be accomplished by using data-modal-ajax="url"
on your button. This requires no modal component as it will use a global ajax-ready modal.
<button data-modal-ajax="/styleguide/getting_started" class="button">Load modal with ajax content</button>
You can programatically load content in to the ajax modal with Ornament.C.Modal.openAjaxModal(url);
.
Sizing
You can set the size of ajax modals using data-modal-size
:
<button data-modal-ajax="/styleguide/getting_started" data-modal-size="small" class="button">Load small ajax modal</button>
Error state
<button data-modal-ajax="/bad-url" class="button">Load modal with ajax content</button>
Templating ajax views
Any URLs loaded in to the ajax modal will get an extra URL parameter modal=true
, you can use this parameter to render the resulting view with the modal
rails layout.
If using Koi, I'd recommend modifying the layout_by_resource
method in common_controller_actions
to check for this param and return the modal layout.
This layout provides the header and body structure for seamless modal integration.
Keep in mind that if you customise the layout of ControlledModal.jsx
, you'll likely want to update layouts/modal.html.erb
to match.
Ajaxed components
Be aware that if your ajaxed content has javascript components you may need to reinitialise them.
This includes react components, which can be remounted using ReactOnRails.reactOnRailsPageLoaded();
.
Most Ornament components have an init
function you can call, eg. Ornament.C.FormsEnhanced.init()
.
Videos
You can use a video
prop instead of the other render props to load in a responsive iframe:
<button data-modal="video-youtube-iframe" class="button">Open video from youtube</button> <%= react_component "Modal", props: { id: "video-youtube-iframe", label: "Video", video: "https://www.youtube.com/embed/6ZfuNTqbHE8", } %>
This prop is strictly an iframe and can be any sort of iframe. The only limitation is that it's wrapped in a responsive video wrapper so it's aspect ratio will be that of a video. For example here is one from vimeo.
<button data-modal="video-vimeo-iframe" class="button">Open video from vimeo</button> <%= react_component "Modal", props: { id: "video-vimeo-iframe", label: "Video", video: "https://player.vimeo.com/video/112866269", } %>
With Youtube id
If you're using a youtube video, you can use the youtube video ID and take advantage of the pre-baked youtube configuration by using youtube
prop instead.
Using noHeader
is recommended for a more cinematic experience, but left optional.
<button data-modal="video-youtube" class="button">Open video from youtube</button> <%= react_component "Modal", props: { id: "video-youtube", label: "Video", youtube: "6ZfuNTqbHE8", noHeader: true, } %>
Videos are automatically ratio sized to 16x9, you can specify a custom ratio as a string with the ratio
prop.
Custom close buttons
You can create a custom close button by adding data-modal-close
on any button.
<button data-modal="custom-close" class="button">Open modal</button> <%= react_component "Modal", props: { id: "custom-close", label: "Custom close button", renderHTML: modal_content + "<div class='panel--padding content'><button class='button__cancel' data-modal-close>Custom close button</button></div>".html_safe, } %>
Linking Modals
Add data-modal-link
to your button to add close any open modals, wait, then open the requested modal.
<button data-modal="linking-one" class="button">Open modal</button> <%= react_component "Modal", props: { id: "linking-one", label: "Modal #1", renderHTML: "<div class='panel--padding content'><button class='button' data-modal='linking-two' data-modal-link>Go to modal #2</button></div>".html_safe, } %> <%= react_component "Modal", props: { id: "linking-two", label: "Modal #2", renderHTML: "<div class='panel--padding content'><button class='button' data-modal='linking-one' data-modal-link>Go to modal #1</button> or <button class='button__cancel' data-modal-close>Close button</button></div>".html_safe, } %>
Footers
You can provide a fixed footer using either HTML string in footerHTML
or a react component in footer
.
<button data-modal="with-footer" class="button">Open modal</button> <%= react_component "Modal", props: { id: "with-footer", label: "Modal with footer", footerHTML: footer_content, renderHTML: modal_content, } %>
React compositing
Sometimes you need really complex javascript-driven modals, or modals inside react components.
Here is a very simple example of creating a user component that shows user details in a modal using a controlled modal, local state and render props:
import React from "react"; import ControlledModal from '../Modal/ControlledModal'; export default class UserListItem extends React.Component { constructor(props) { super(props); this.state = { modalOpen: false, } } openModal = () => { this.setState({ modalOpen: true, }); } closeModal = () => { this.setState({ modalOpen: false, }) } render(){ <div> <strong>{this.props.userName}</strong> <button onclick={this.openModal}>View more details</button> <ControlledModal open={this.state.modalOpen} onClose={this.props.closeModal} className="ReactModal__User" render={() => ( <div> <h2>{this.props.userName}</h2> <div className="content" dangerouslySetInnerHTML={{__html: this.props.userContent }} /> </div> )} </> </div> } }
ShadowScroller Component
The custom react modal uses a custom content wrapper component called ShadowScroller
, which automatically adds shadows to the top and bottom of scrollable areas when the content is overflowed.
ShadowScroller can be utilised for other scrollable areas in two ways:
React ref
If your scrollable div is in React land, you can use the scrollRef
function to target the scrollable area:
<ShadowScroller render={(scrollRef) => ( <div ref={el => scrollRef(el)}> Your scrollable content </div> )} />
DOM selector
However, if your scrollable area is not in React land, say you're using dangerouslySetInnerHTML
to dump a bunch of HTML in a react component and THAT has a scrollable area in it, you can use the second parameter of the wrapper component, domRef
to target a selector:
<ShadowScroller render={(scrollRef, domRef) => ( <div ref={el => domRef(el, ".my-scrollable-area")} dangerouslySetInnerHTML={{__html: this.props.htmlContent}} ></div> )} />