Skip to content
This repository was archived by the owner on Jan 13, 2025. It is now read-only.

Commit 4be947f

Browse files
authored
feat(snackbar): Implement full-featured Snackbar component (#852)
BREAKING CHANGE: Adds adapter methods to capture blur, focus, and interaction events
1 parent 28bf7a9 commit 4be947f

File tree

8 files changed

+555
-77
lines changed

8 files changed

+555
-77
lines changed

demos/snackbar.html

Lines changed: 100 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -27,7 +27,15 @@
2727
<link rel="stylesheet" href="https://fonts.googleapis.com/css?family=Roboto:300,400,500">
2828
<style>
2929
/* initialize it off screen. */
30-
.loading .example .mdc-snackbar { transform: translateY(100%); }
30+
.loading .example .mdc-snackbar { transform: translateY(200%); }
31+
32+
.mdc-theme--dark {
33+
background-color: #333;
34+
}
35+
36+
.mdc-theme--dark .hero {
37+
background-color: #2d2d2d;
38+
}
3139

3240
/* Override style for hero example. */
3341
.hero .mdc-snackbar {
@@ -43,6 +51,9 @@
4351
padding-bottom: 8px;
4452
}
4553

54+
.demo-activate-button {
55+
margin-top: 14px;
56+
}
4657

4758
</style>
4859
</head>
@@ -88,33 +99,26 @@ <h2 class="mdc-typography--title">Basic Example</h2>
8899
</div>
89100
<label for="multiline" id="multiline-label">Multiline</label>
90101
</div>
102+
<br/>
91103

92-
<div class="mdc-checkbox-wrapper">
93-
<div class="mdc-checkbox-wrapper__layout">
94-
<div class="mdc-checkbox">
95-
<input type="checkbox"
96-
class="mdc-checkbox__native-control"
97-
id="action-on-bottom"
98-
aria-labelledby="action-on-bottom-label" />
99-
<div class="mdc-checkbox__background">
104+
<div class="mdc-form-field">
105+
<div class="mdc-checkbox" id="action-on-bottom-checkbox">
106+
<input type="checkbox" class="mdc-checkbox__native-control" id="action-on-bottom" aria-labelledby="action-on-bottom-label" />
107+
<div class="mdc-checkbox__background">
100108
<svg class="mdc-checkbox__checkmark" viewBox="0 0 24 24">
101109
<path class="mdc-checkbox__checkmark__path" fill="none" stroke="white"
102-
d="M1.73,12.91 8.1,19.28 22.79,4.59"/>
110+
d="M1.73,12.91 8.1,19.28 22.79,4.59"/>
103111
</svg>
104112
<div class="mdc-checkbox__mixedmark"></div>
105113
</div>
106114
</div>
107115
<label for="action-on-bottom" id="action-on-bottom-label">Action On Bottom</label>
108116
</div>
117+
<br/>
109118

110-
<div class="mdc-checkbox-wrapper">
111-
<div class="mdc-checkbox-wrapper__layout">
119+
<div class="mdc-form-field">
112120
<div class="mdc-checkbox">
113-
<input type="checkbox"
114-
checked
115-
class="mdc-checkbox__native-control"
116-
id="dismiss-on-action"
117-
aria-labelledby="dismiss-on-action-label" />
121+
<input type="checkbox" checked class="mdc-checkbox__native-control" id="dismiss-on-action" aria-labelledby="dismiss-on-action-label" />
118122
<div class="mdc-checkbox__background">
119123
<svg class="mdc-checkbox__checkmark" viewBox="0 0 24 24">
120124
<path class="mdc-checkbox__checkmark__path" fill="none" stroke="white"
@@ -125,19 +129,27 @@ <h2 class="mdc-typography--title">Basic Example</h2>
125129
</div>
126130
<label for="dismiss-on-action" id="dismiss-on-action-label">Dismiss On Action</label>
127131
</div>
132+
<br/>
128133

129-
<div class="field">
130-
<label for="message">Message Text</label>
131-
<input type="text" id="message" value="Message deleted">
134+
<button type="button" class="mdc-button mdc-button--raised mdc-button--primary" id="toggle-dark-theme">Toggle Dark Theme</button>
135+
<br/>
136+
137+
<div class="mdc-textfield">
138+
<input type="text" id="message" class="mdc-textfield__input" value="Message deleted">
139+
<label class="mdc-textfield__label" for="message">Message Text</label>
132140
</div>
141+
<br/>
133142

134-
<div class="field">
135-
<label for="action">Action Text</label>
136-
<input type="text" id="action" value="Undo">
143+
<div class="mdc-textfield">
144+
<input type="text" id="action" class="mdc-textfield__input" value="Undo">
145+
<label class="mdc-textfield__label" for="action">Action Text</label>
137146
</div>
147+
<br/>
138148

139-
<button type="button" id="show-snackbar">Show</button>
140-
<button type="button" id="show-rtl-snackbar">Show RTL</button>
149+
<button type="button" class="demo-activate-button mdc-button mdc-button--raised mdc-button--secondary" id="show-snackbar">Show</button>
150+
<button type="button" class="demo-activate-button mdc-button mdc-button--raised mdc-button--secondary" id="show-rtl-snackbar">Show RTL</button>
151+
<button type="button" class="demo-activate-button mdc-button mdc-button--raised mdc-button--secondary" id="show-start-aligned-snackbar">Show Start Aligned</button>
152+
<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>
141153

142154
<div id="mdc-js-snackbar"
143155
class="mdc-snackbar demo-hidden"
@@ -161,6 +173,28 @@ <h2 class="mdc-typography--title">Basic Example</h2>
161173
</div>
162174
</div>
163175
</div>
176+
<div id="mdc-align-start-js-snackbar"
177+
class="mdc-snackbar mdc-snackbar--align-start demo-hidden"
178+
aria-live="assertive"
179+
aria-atomic="true"
180+
aria-hidden="true">
181+
<div class="mdc-snackbar__text"></div>
182+
<div class="mdc-snackbar__action-wrapper">
183+
<button type="button" class="mdc-button mdc-snackbar__action-button"></button>
184+
</div>
185+
</div>
186+
<div dir="rtl">
187+
<div id="mdc-align-start-rtl-js-snackbar"
188+
class="mdc-snackbar mdc-snackbar--align-start demo-hidden"
189+
aria-live="assertive"
190+
aria-atomic="true"
191+
aria-hidden="true">
192+
<div class="mdc-snackbar__text"></div>
193+
<div class="mdc-snackbar__action-wrapper">
194+
<button type="button" class="mdc-button mdc-snackbar__action-button"></button>
195+
</div>
196+
</div>
197+
</div>
164198
</div>
165199
</section>
166200
</main>
@@ -171,28 +205,52 @@ <h2 class="mdc-typography--title">Basic Example</h2>
171205
var MDCSnackbar = global.mdc.snackbar.MDCSnackbar;
172206
var snackbar = new MDCSnackbar(document.getElementById('mdc-js-snackbar'));
173207
var rtlSnackbar = new MDCSnackbar(document.getElementById('mdc-rtl-js-snackbar'));
208+
var alignStartSnackbar = new MDCSnackbar(document.getElementById('mdc-align-start-js-snackbar'));
209+
var alignStartRTLSnackbar = new MDCSnackbar(document.getElementById('mdc-align-start-rtl-js-snackbar'));
174210
var messageInput = document.getElementById('message');
175211
var actionInput = document.getElementById('action');
176212
var multilineInput = document.getElementById('multiline');
177213
var actionOnBottomInput = document.getElementById('action-on-bottom');
214+
var actionOnBottomCheckbox = document.getElementById('action-on-bottom-checkbox');
178215
var dismissOnActionInput = document.getElementById('dismiss-on-action');
216+
var textFields = document.querySelectorAll('.mdc-textfield');
217+
218+
// Since Action on Bottom cannot be checked if Multi-line Input
219+
// is not, we start with a disabled Action on Bottom option
220+
actionOnBottomCheckbox.classList.add('mdc-checkbox--disabled');
221+
actionOnBottomInput.disabled = true;
222+
actionOnBottomInput.checked = false;
179223

180224
var show = function(sb) {
181225
snackbar.dismissesOnAction = dismissOnActionInput.checked;
182226
var data = {
183227
message: messageInput.value,
184228
actionOnBottom: actionOnBottomInput.checked,
185-
multiline: multilineInput.checked
229+
multiline: multilineInput.checked,
230+
timeout: 2750
186231
};
232+
187233
if (actionInput.value) {
188234
data.actionText = actionInput.value;
189235
data.actionHandler = function() {
190236
console.log(data);
191237
}
192238
}
239+
193240
sb.show(data);
194241
};
195242

243+
multilineInput.addEventListener('click', function () {
244+
if (!multilineInput.checked) {
245+
actionOnBottomCheckbox.classList.add('mdc-checkbox--disabled');
246+
actionOnBottomInput.disabled = true;
247+
actionOnBottomInput.checked = false;
248+
} else {
249+
actionOnBottomCheckbox.classList.remove('mdc-checkbox--disabled');
250+
actionOnBottomInput.disabled = false;
251+
}
252+
});
253+
196254
document.getElementById('show-snackbar').addEventListener('click', function() {
197255
show(snackbar);
198256
});
@@ -201,6 +259,22 @@ <h2 class="mdc-typography--title">Basic Example</h2>
201259
show(rtlSnackbar);
202260
});
203261

262+
document.getElementById('show-start-aligned-snackbar').addEventListener('click', function() {
263+
show(alignStartSnackbar);
264+
});
265+
266+
document.getElementById('show-start-aligned-rtl-snackbar').addEventListener('click', function() {
267+
show(alignStartRTLSnackbar);
268+
});
269+
270+
document.getElementById('toggle-dark-theme').addEventListener('click', function(evt) {
271+
document.body.classList.contains('mdc-theme--dark') ? document.body.classList.remove('mdc-theme--dark') : document.body.classList.add('mdc-theme--dark');
272+
});
273+
274+
textFields.forEach(function(tf) {
275+
mdc.textfield.MDCTextfield.attachTo(tf);
276+
})
277+
204278
// Remove any element hiding after loading.
205279
window.onload = function() { document.body.className = ''; };
206280
})(this);

packages/mdc-snackbar/README.md

Lines changed: 60 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,7 @@ path: /catalog/snackbars/
1818

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

2323
## Design & API Documentation
2424

@@ -53,6 +53,23 @@ npm install --save @material/snackbar
5353
</div>
5454
```
5555

56+
### Start Aligned Snackbars (tablet and desktop only)
57+
58+
MDC Snackbar can be start aligned (including in RTL contexts). To create a start-aligned
59+
snackbar, add the `mdc-snackbar--align-start` modifier class to the root element.
60+
61+
```html
62+
<div class="mdc-snackbar mdc-snackbar--align-start"
63+
aria-live="assertive"
64+
aria-atomic="true"
65+
aria-hidden="true">
66+
<div class="mdc-snackbar__text"></div>
67+
<div class="mdc-snackbar__action-wrapper">
68+
<button type="button" class="mdc-button mdc-snackbar__action-button"></button>
69+
</div>
70+
</div>
71+
```
72+
5673
### Using the JS Component
5774

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

145+
### Responding to a Snackbar Action
146+
147+
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.
148+
149+
```html
150+
<div class="mdc-snackbar"
151+
aria-live="assertive"
152+
aria-atomic="true"
153+
aria-hidden="true">
154+
<div class="mdc-snackbar__text"></div>
155+
<div class="mdc-snackbar__action-wrapper">
156+
<button type="button" class="mdc-button mdc-snackbar__action-button"></button>
157+
</div>
158+
</div>
159+
```
160+
161+
```js
162+
import {MDCSnackbar} from 'mdc-snackbar';
163+
164+
const snackbar = new MDCSnackbar(document.querySelector('.mdc-snackbar'));
165+
const dataObj = {
166+
message: messageInput.value,
167+
actionText: 'Undo',
168+
actionHandler: function () {
169+
console.log('my cool function');
170+
}
171+
};
172+
173+
snackbar.show(dataObj);
174+
```
175+
176+
128177
### Keep snackbar when the action button is pressed
129178

130179
By default the snackbar will be dimissed when the user presses the action button.
@@ -149,10 +198,18 @@ The adapter for snackbars must provide the following functions, with correct sig
149198
| `removeClass(className: string) => void` | Removes a class from the root element. |
150199
| `setAriaHidden() => void` | Sets `aria-hidden="true"` on the root element. |
151200
| `unsetAriaHidden() => void` | Removes the `aria-hidden` attribute from the root element. |
152-
| `setMessageText(message: string) => void` | Set the text content of the message element. |
153-
| `setActionText(actionText: string) => void` | Set the text content of the action element. |
154201
| `setActionAriaHidden() => void` | Sets `aria-hidden="true"` on the action element. |
155202
| `unsetActionAriaHidden() => void` | Removes the `aria-hidden` attribute from the action element. |
203+
| `setActionText(actionText: string) => void` | Set the text content of the action element. |
204+
| `setMessageText(message: string) => void` | Set the text content of the message element. |
205+
| `setFocus() => void` | Sets focus on the action button. |
206+
| `visibilityIsHidden() => boolean` | Returns document.hidden property. |
207+
| `registerBlurHandler(handler: EventListener) => void` | Registers an event handler to be called when a `blur` event is triggered on the action button |
208+
| `deregisterBlurHandler(handler: EventListener) => void` | Deregisters a `blur` event handler from the actionButton |
209+
| `registerVisibilityChangeHandler(handler: EventListener) => void` | Registers an event handler to be called when a 'visibilitychange' event occurs |
210+
| `deregisterVisibilityChangeHandler(handler: EventListener) => void` | Deregisters an event handler to be called when a 'visibilitychange' event occurs |
211+
| `registerCapturedInteractionHandler(evtType: string, handler: EventListener) => void` | Registers an event handler to be called when the given event type is triggered on the `body` |
212+
| `deregisterCapturedInteractionHandler(evtType: string, handler: EventListener) => void` | Deregisters an event handler from the `body` |
156213
| `registerActionClickHandler(handler: EventListener) => void` | Registers an event handler to be called when a `click` event is triggered on the action element. |
157214
| `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. |
158215
| `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. |

packages/mdc-snackbar/_variables.scss

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@
1616

1717
// Hard coded since the color is not present in any palette.
1818
$mdc-snackbar-background-color: #323232;
19+
$mdc-snackbar-background-color-on-dark: #fafafa;
1920
$mdc-snackbar-foreground-color: white;
2021
// TODO: Better spot to pull this breakpoint?
2122
//$snackbar-tablet-breakpoint: $grid-tablet-breakpoint;

0 commit comments

Comments
 (0)