Skip to content

Commit 566fa13

Browse files
committed
feat: new Banner component
1 parent c1a186d commit 566fa13

20 files changed

+1626
-187
lines changed

README.md

+3-2
Original file line numberDiff line numberDiff line change
@@ -222,6 +222,7 @@ I've only done components that need to/can be Svelte-ified. For some things, lik
222222

223223
Click a component below to go to its documentation. (Note that this documentation is a work in progress. The demo code should be your main source of truth for how something works.)
224224

225+
- [Banner](https://github.com/hperrin/svelte-material-ui/blob/master/packages/banner/README.md)
225226
- [Buttons](https://github.com/hperrin/svelte-material-ui/blob/master/packages/button/README.md)
226227
- [Floating Action Buttons](https://github.com/hperrin/svelte-material-ui/blob/master/packages/fab/README.md)
227228
- [Icon Buttons](https://github.com/hperrin/svelte-material-ui/blob/master/packages/icon-button/README.md)
@@ -263,9 +264,9 @@ Click a component below to go to its documentation. (Note that this documentatio
263264
- [Top App Bar](https://github.com/hperrin/svelte-material-ui/blob/master/packages/top-app-bar/README.md)
264265
- [Typography](https://material.io/develop/web/components/typography/)
265266

266-
New components from the upstream library that haven't been built yet:
267+
New components from the upstream library to build for SMUI v3:
267268

268-
- [ ] Banners
269+
- [x] Banners
269270
- [ ] Circular Progress
270271
- [ ] Layout Grids (Not new to MDC, but I haven't made a component for it.)
271272
- [ ] Segmented Buttons (This is different than SMUI's button groups.)

packages/banner/Banner.svelte

+211
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,211 @@
1+
<svelte:window on:resize={layout} />
2+
3+
<div
4+
bind:this={element}
5+
use:useActions={use}
6+
use:forwardEvents
7+
class={classMap({
8+
[className]: true,
9+
'mdc-banner': true,
10+
'mdc-banner--centered': centered,
11+
'mdc-banner--mobile-stacked': mobileStacked,
12+
...internalClasses,
13+
})}
14+
style={Object.entries(internalStyles)
15+
.map(([name, value]) => `${name}: ${value};`)
16+
.concat([style])
17+
.join(' ')}
18+
role="banner"
19+
on:SMUI:banner:button:primaryActionClick={() =>
20+
instance && instance.handlePrimaryActionClick()}
21+
on:SMUI:banner:button:secondaryActionClick={() =>
22+
instance && instance.handleSecondaryActionClick()}
23+
{...exclude($$restProps, ['content$', 'textWrapper$', 'graphic$'])}
24+
>
25+
<Fixed bind:fixed>
26+
<div
27+
bind:this={content}
28+
class={classMap({
29+
[content$class]: true,
30+
'mdc-banner__content': true,
31+
})}
32+
role="status"
33+
aria-live="assertive"
34+
{...prefixFilter($$restProps, 'content$')}
35+
>
36+
{#if $$slots.icon || $$slots.label}
37+
<div
38+
class={classMap({
39+
[textWrapper$class]: true,
40+
'mdc-banner__graphic-text-wrapper': true,
41+
})}
42+
{...prefixFilter($$restProps, 'textWrapper$')}
43+
>
44+
{#if $$slots.icon}
45+
<div
46+
class={classMap({
47+
[graphic$class]: true,
48+
'mdc-banner__graphic': true,
49+
})}
50+
role="img"
51+
alt=""
52+
{...prefixFilter($$restProps, 'graphic$')}
53+
>
54+
<slot name="icon" />
55+
</div>
56+
{/if}
57+
<slot name="label" />
58+
</div>
59+
{/if}
60+
{#if $$slots.actions}
61+
<div class="mdc-banner__actions">
62+
<slot name="actions" />
63+
</div>
64+
{/if}
65+
</div>
66+
</Fixed>
67+
</div>
68+
69+
<script>
70+
import { MDCBannerFoundation } from '@material/banner';
71+
import { onMount, onDestroy, getContext, setContext, tick } from 'svelte';
72+
import { get_current_component } from 'svelte/internal';
73+
import {
74+
forwardEventsBuilder,
75+
classMap,
76+
exclude,
77+
prefixFilter,
78+
useActions,
79+
dispatch,
80+
} from '@smui/common/internal.js';
81+
import Fixed from './Fixed.svelte';
82+
83+
const forwardEvents = forwardEventsBuilder(get_current_component(), [
84+
'MDCBanner:closed',
85+
'MDCBanner:closing',
86+
'MDCBanner:opened',
87+
'MDCBanner:opening',
88+
]);
89+
90+
export let use = [];
91+
let className = '';
92+
export { className as class };
93+
export let style = '';
94+
export let open = false;
95+
export let centered = false;
96+
export let fixed = false;
97+
export let mobileStacked = false;
98+
export let content$class = '';
99+
export let textWrapper$class = '';
100+
export let graphic$class = '';
101+
102+
let element;
103+
let instance;
104+
let internalClasses = {};
105+
let internalStyles = {};
106+
let content;
107+
let addLayoutListener = getContext('SMUI:addLayoutListener');
108+
let removeLayoutListener;
109+
110+
setContext('SMUI:label:context', 'banner');
111+
setContext('SMUI:icon:context', 'banner');
112+
setContext('SMUI:button:context', 'banner');
113+
114+
$: if (instance && instance.isOpen() !== open) {
115+
if (open) {
116+
instance.open();
117+
} else {
118+
instance.close();
119+
}
120+
}
121+
122+
let previousMobileStacked = mobileStacked;
123+
$: if (previousMobileStacked !== mobileStacked) {
124+
previousMobileStacked = mobileStacked;
125+
tick().then(layout);
126+
}
127+
128+
if (addLayoutListener) {
129+
removeLayoutListener = addLayoutListener(layout);
130+
}
131+
132+
onMount(() => {
133+
instance = new MDCBannerFoundation({
134+
addClass,
135+
getContentHeight: () => {
136+
element.classList.add('smui-banner--force-show');
137+
const offsetHeight = content.offsetHeight;
138+
element.classList.remove('smui-banner--force-show');
139+
return offsetHeight;
140+
},
141+
notifyClosed: (reason) => {
142+
open = false;
143+
dispatch(getElement(), 'MDCBanner:closed', { reason });
144+
},
145+
notifyClosing: (reason) => {
146+
dispatch(getElement(), 'MDCBanner:closing', { reason });
147+
},
148+
notifyOpened: () => {
149+
open = true;
150+
dispatch(getElement(), 'MDCBanner:opened', {});
151+
},
152+
notifyOpening: () => {
153+
dispatch(getElement(), 'MDCBanner:opening', {});
154+
},
155+
removeClass,
156+
setStyleProperty: addStyle,
157+
});
158+
159+
instance.init();
160+
instance.layout();
161+
162+
return () => {
163+
instance.destroy();
164+
};
165+
});
166+
167+
onDestroy(() => {
168+
if (removeLayoutListener) {
169+
removeLayoutListener();
170+
}
171+
});
172+
173+
function addClass(className) {
174+
if (!internalClasses[className]) {
175+
internalClasses[className] = true;
176+
}
177+
}
178+
179+
function removeClass(className) {
180+
if (!(className in internalClasses) || internalClasses[className]) {
181+
internalClasses[className] = false;
182+
}
183+
}
184+
185+
function addStyle(name, value) {
186+
if (internalStyles[name] != value) {
187+
if (value === '' || value == null) {
188+
delete internalStyles[name];
189+
internalStyles = internalStyles;
190+
} else {
191+
internalStyles[name] = value;
192+
}
193+
}
194+
}
195+
196+
export function isOpen() {
197+
return open;
198+
}
199+
200+
export function setOpen(value) {
201+
open = value;
202+
}
203+
204+
export function layout() {
205+
instance.layout();
206+
}
207+
208+
export function getElement() {
209+
return element;
210+
}
211+
</script>

0 commit comments

Comments
 (0)