Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat(snackbar): Implement full-featured Snackbar component #852

Merged
merged 10 commits into from
Jul 7, 2017
126 changes: 100 additions & 26 deletions demos/snackbar.html
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,15 @@
<link rel="stylesheet" href="https://fonts.googleapis.com/css?family=Roboto:300,400,500">
<style>
/* initialize it off screen. */
.loading .example .mdc-snackbar { transform: translateY(100%); }
.loading .example .mdc-snackbar { transform: translateY(200%); }

.mdc-theme--dark {
background-color: #333;
}

.mdc-theme--dark .hero {
background-color: #2d2d2d;
}

/* Override style for hero example. */
.hero .mdc-snackbar {
Expand All @@ -43,6 +51,9 @@
padding-bottom: 8px;
}

.demo-activate-button {
margin-top: 14px;
}

</style>
</head>
Expand Down Expand Up @@ -88,33 +99,26 @@ <h2 class="mdc-typography--title">Basic Example</h2>
</div>
<label for="multiline" id="multiline-label">Multiline</label>
</div>
<br/>

<div class="mdc-checkbox-wrapper">
<div class="mdc-checkbox-wrapper__layout">
<div class="mdc-checkbox">
<input type="checkbox"
class="mdc-checkbox__native-control"
id="action-on-bottom"
aria-labelledby="action-on-bottom-label" />
<div class="mdc-checkbox__background">
<div class="mdc-form-field">
<div class="mdc-checkbox" id="action-on-bottom-checkbox">
<input type="checkbox" class="mdc-checkbox__native-control" id="action-on-bottom" aria-labelledby="action-on-bottom-label" />
<div class="mdc-checkbox__background">
<svg class="mdc-checkbox__checkmark" viewBox="0 0 24 24">
<path class="mdc-checkbox__checkmark__path" fill="none" stroke="white"
d="M1.73,12.91 8.1,19.28 22.79,4.59"/>
d="M1.73,12.91 8.1,19.28 22.79,4.59"/>
</svg>
<div class="mdc-checkbox__mixedmark"></div>
</div>
</div>
<label for="action-on-bottom" id="action-on-bottom-label">Action On Bottom</label>
</div>
<br/>

<div class="mdc-checkbox-wrapper">
<div class="mdc-checkbox-wrapper__layout">
<div class="mdc-form-field">
<div class="mdc-checkbox">
<input type="checkbox"
checked
class="mdc-checkbox__native-control"
id="dismiss-on-action"
aria-labelledby="dismiss-on-action-label" />
<input type="checkbox" checked class="mdc-checkbox__native-control" id="dismiss-on-action" aria-labelledby="dismiss-on-action-label" />
<div class="mdc-checkbox__background">
<svg class="mdc-checkbox__checkmark" viewBox="0 0 24 24">
<path class="mdc-checkbox__checkmark__path" fill="none" stroke="white"
Expand All @@ -125,19 +129,27 @@ <h2 class="mdc-typography--title">Basic Example</h2>
</div>
<label for="dismiss-on-action" id="dismiss-on-action-label">Dismiss On Action</label>
</div>
<br/>

<div class="field">
<label for="message">Message Text</label>
<input type="text" id="message" value="Message deleted">
<button type="button" class="mdc-button mdc-button--raised mdc-button--primary" id="toggle-dark-theme">Toggle Dark Theme</button>
<br/>

<div class="mdc-textfield">
<input type="text" id="message" class="mdc-textfield__input" value="Message deleted">
<label class="mdc-textfield__label" for="message">Message Text</label>
</div>
<br/>

<div class="field">
<label for="action">Action Text</label>
<input type="text" id="action" value="Undo">
<div class="mdc-textfield">
<input type="text" id="action" class="mdc-textfield__input" value="Undo">
<label class="mdc-textfield__label" for="action">Action Text</label>
</div>
<br/>

<button type="button" id="show-snackbar">Show</button>
<button type="button" id="show-rtl-snackbar">Show RTL</button>
<button type="button" class="demo-activate-button mdc-button mdc-button--raised mdc-button--secondary" id="show-snackbar">Show</button>
<button type="button" class="demo-activate-button mdc-button mdc-button--raised mdc-button--secondary" id="show-rtl-snackbar">Show RTL</button>
<button type="button" class="demo-activate-button mdc-button mdc-button--raised mdc-button--secondary" id="show-start-aligned-snackbar">Show Start Aligned</button>
<button type="button" class="demo-activate-button mdc-button mdc-button--raised mdc-button--secondary" id="show-start-aligned-rtl-snackbar">Show Start Aligned (RTL)</button>

<div id="mdc-js-snackbar"
class="mdc-snackbar demo-hidden"
Expand All @@ -161,6 +173,28 @@ <h2 class="mdc-typography--title">Basic Example</h2>
</div>
</div>
</div>
<div id="mdc-align-start-js-snackbar"
class="mdc-snackbar mdc-snackbar--align-start demo-hidden"
aria-live="assertive"
aria-atomic="true"
aria-hidden="true">
<div class="mdc-snackbar__text"></div>
<div class="mdc-snackbar__action-wrapper">
<button type="button" class="mdc-button mdc-snackbar__action-button"></button>
</div>
</div>
<div dir="rtl">
<div id="mdc-align-start-rtl-js-snackbar"
class="mdc-snackbar mdc-snackbar--align-start demo-hidden"
aria-live="assertive"
aria-atomic="true"
aria-hidden="true">
<div class="mdc-snackbar__text"></div>
<div class="mdc-snackbar__action-wrapper">
<button type="button" class="mdc-button mdc-snackbar__action-button"></button>
</div>
</div>
</div>
</div>
</section>
</main>
Expand All @@ -171,28 +205,52 @@ <h2 class="mdc-typography--title">Basic Example</h2>
var MDCSnackbar = global.mdc.snackbar.MDCSnackbar;
var snackbar = new MDCSnackbar(document.getElementById('mdc-js-snackbar'));
var rtlSnackbar = new MDCSnackbar(document.getElementById('mdc-rtl-js-snackbar'));
var alignStartSnackbar = new MDCSnackbar(document.getElementById('mdc-align-start-js-snackbar'));
var alignStartRTLSnackbar = new MDCSnackbar(document.getElementById('mdc-align-start-rtl-js-snackbar'));
var messageInput = document.getElementById('message');
var actionInput = document.getElementById('action');
var multilineInput = document.getElementById('multiline');
var actionOnBottomInput = document.getElementById('action-on-bottom');
var actionOnBottomCheckbox = document.getElementById('action-on-bottom-checkbox');
var dismissOnActionInput = document.getElementById('dismiss-on-action');
var textFields = document.querySelectorAll('.mdc-textfield');

// Since Action on Bottom cannot be checked if Multi-line Input
// is not, we start with a disabled Action on Bottom option
actionOnBottomCheckbox.classList.add('mdc-checkbox--disabled');
actionOnBottomInput.disabled = true;
actionOnBottomInput.checked = false;

var show = function(sb) {
snackbar.dismissesOnAction = dismissOnActionInput.checked;
var data = {
message: messageInput.value,
actionOnBottom: actionOnBottomInput.checked,
multiline: multilineInput.checked
multiline: multilineInput.checked,
timeout: 2750
};

if (actionInput.value) {
data.actionText = actionInput.value;
data.actionHandler = function() {
console.log(data);
}
}

sb.show(data);
};

multilineInput.addEventListener('click', function () {
if (!multilineInput.checked) {
actionOnBottomCheckbox.classList.add('mdc-checkbox--disabled');
actionOnBottomInput.disabled = true;
actionOnBottomInput.checked = false;
} else {
actionOnBottomCheckbox.classList.remove('mdc-checkbox--disabled');
actionOnBottomInput.disabled = false;
}
});

document.getElementById('show-snackbar').addEventListener('click', function() {
show(snackbar);
});
Expand All @@ -201,6 +259,22 @@ <h2 class="mdc-typography--title">Basic Example</h2>
show(rtlSnackbar);
});

document.getElementById('show-start-aligned-snackbar').addEventListener('click', function() {
show(alignStartSnackbar);
});

document.getElementById('show-start-aligned-rtl-snackbar').addEventListener('click', function() {
show(alignStartRTLSnackbar);
});

document.getElementById('toggle-dark-theme').addEventListener('click', function(evt) {
document.body.classList.contains('mdc-theme--dark') ? document.body.classList.remove('mdc-theme--dark') : document.body.classList.add('mdc-theme--dark');
});

textFields.forEach(function(tf) {
mdc.textfield.MDCTextfield.attachTo(tf);
})

// Remove any element hiding after loading.
window.onload = function() { document.body.className = ''; };
})(this);
Expand Down
63 changes: 60 additions & 3 deletions packages/mdc-snackbar/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@ path: /catalog/snackbars/

The MDC Snackbar component is a spec-aligned snackbar/toast component adhering to the
[Material Design snackbars & toasts requirements](https://material.io/guidelines/components/snackbars-toasts.html#snackbars-toasts-specs).
It requires JavaScript the trigger the display and hide of the snackbar.
It requires JavaScript to show and hide itself.

## Design & API Documentation

Expand Down Expand Up @@ -53,6 +53,23 @@ npm install --save @material/snackbar
</div>
```

### Start Aligned Snackbars (tablet and desktop only)

MDC Snackbar can be start aligned (including in RTL contexts). To create a start-aligned
snackbar, add the `mdc-snackbar--align-start` modifier class to the root element.

```html
<div class="mdc-snackbar mdc-snackbar--align-start"
aria-live="assertive"
aria-atomic="true"
aria-hidden="true">
<div class="mdc-snackbar__text"></div>
<div class="mdc-snackbar__action-wrapper">
<button type="button" class="mdc-button mdc-snackbar__action-button"></button>
</div>
</div>
```

### Using the JS Component

MDC Snackbar ships with a Component / Foundation combo which provides the API for showing snackbar
Expand Down Expand Up @@ -125,6 +142,38 @@ properties and their usage.
| multiline | Whether to show the snackbar with space for multiple lines of text | Optional | Boolean |
| actionOnBottom | Whether to show the action below the multiple lines of text | Optional, applies when multiline is true | Boolean |

### Responding to a Snackbar Action

To respond to a snackbar action, assign a function to the optional `actionHandler` property in the object that gets passed to the `show` method. If you choose to set this property, you *must _also_* set the `actionText` property.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

nit: Could we also document it will throw an error in that case?

```html
<div class="mdc-snackbar"
aria-live="assertive"
aria-atomic="true"
aria-hidden="true">
<div class="mdc-snackbar__text"></div>
<div class="mdc-snackbar__action-wrapper">
<button type="button" class="mdc-button mdc-snackbar__action-button"></button>
</div>
</div>
```

```js
import {MDCSnackbar} from 'mdc-snackbar';

const snackbar = new MDCSnackbar(document.querySelector('.mdc-snackbar'));
const dataObj = {
message: messageInput.value,
actionText: 'Undo',
actionHandler: function () {
console.log('my cool function');
}
};

snackbar.show(dataObj);
```


### Keep snackbar when the action button is pressed

By default the snackbar will be dimissed when the user presses the action button.
Expand All @@ -149,10 +198,18 @@ The adapter for snackbars must provide the following functions, with correct sig
| `removeClass(className: string) => void` | Removes a class from the root element. |
| `setAriaHidden() => void` | Sets `aria-hidden="true"` on the root element. |
| `unsetAriaHidden() => void` | Removes the `aria-hidden` attribute from the root element. |
| `setMessageText(message: string) => void` | Set the text content of the message element. |
| `setActionText(actionText: string) => void` | Set the text content of the action element. |
| `setActionAriaHidden() => void` | Sets `aria-hidden="true"` on the action element. |
| `unsetActionAriaHidden() => void` | Removes the `aria-hidden` attribute from the action element. |
| `setActionText(actionText: string) => void` | Set the text content of the action element. |
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

super nit: Set => Sets

| `setMessageText(message: string) => void` | Set the text content of the message element. |
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

same thing here :)

| `setFocus() => void` | Sets focus on the action button. |
| `visibilityIsHidden() => boolean` | Returns document.hidden property. |
| `registerBlurHandler(handler: EventListener) => void` | Registers an event handler to be called when a `blur` event is triggered on the action button |
| `deregisterBlurHandler(handler: EventListener) => void` | Deregisters a `blur` event handler from the actionButton |
| `registerVisibilityChangeHandler(handler: EventListener) => void` | Registers an event handler to be called when a 'visibilitychange' event occurs |
| `deregisterVisibilityChangeHandler(handler: EventListener) => void` | Deregisters an event handler to be called when a 'visibilitychange' event occurs |
| `registerCapturedInteractionHandler(evtType: string, handler: EventListener) => void` | Registers an event handler to be called when the given event type is triggered on the `body` |
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Nit: should we call it capture or capturing, that seems better aligned with MDN description and make people aware that these handlers are using useCapture?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don't think so. What this name suggests is that we are doing something with an interaction that has previously been captured (albeit immediately after).

I agree we should be acknowledging that we are listening during the capture phase. 👍

| `deregisterCapturedInteractionHandler(evtType: string, handler: EventListener) => void` | Deregisters an event handler from the `body` |
| `registerActionClickHandler(handler: EventListener) => void` | Registers an event handler to be called when a `click` event is triggered on the action element. |
| `deregisterActionClickHandler(handler: EventListener) => void` | Deregisters an event handler from a `click` event on the action element. This will only be called with handlers that have previously been passed to `registerActionClickHandler` calls. |
| `registerTransitionEndHandler(handler: EventListener) => void` | Registers an event handler to be called when an `transitionend` event is triggered on the root element. Note that you must account for vendor prefixes in order for this to work correctly. |
Expand Down
1 change: 1 addition & 0 deletions packages/mdc-snackbar/_variables.scss
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@

// Hard coded since the color is not present in any palette.
$mdc-snackbar-background-color: #323232;
$mdc-snackbar-background-color-on-dark: #fafafa;
$mdc-snackbar-foreground-color: white;
// TODO: Better spot to pull this breakpoint?
//$snackbar-tablet-breakpoint: $grid-tablet-breakpoint;
Expand Down
Loading