Skip to content

Commit b9f5601

Browse files
committed
#24 kmdc-snackbar module (#59)
1 parent ac4c355 commit b9f5601

File tree

14 files changed

+458
-19
lines changed

14 files changed

+458
-19
lines changed

CHANGELOG.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@
1515
- [x] mdc-radio
1616
- [x] mdc-tooltip
1717
- [x] mdc-segmented-button
18+
- [x] mdc-snackbar
1819
- [x] material-icons
1920

2021
# v0.0.1

README.md

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -71,7 +71,7 @@ fun Sample() {
7171

7272
### Progress
7373

74-
Here's a tracker list of currently completed *material-components-web* modules (23/49):
74+
Here's a tracker list of currently completed *material-components-web* modules (24/49):
7575

7676
- [ ] mdc-animation (SASS)
7777
- [x] mdc-auto-init (won't wrap)
@@ -109,7 +109,7 @@ Here's a tracker list of currently completed *material-components-web* modules (
109109
- [ ] mdc-select
110110
- [ ] mdc-shape (SASS)
111111
- [ ] mdc-slider
112-
- [ ] mdc-snackbar
112+
- [x] mdc-snackbar
113113
- [ ] mdc-switch
114114
- [ ] mdc-tab-bar
115115
- [ ] mdc-tab-indicator

kmdc/kmdc-core/src/jsMain/kotlin/util.kt

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,10 @@
11
package dev.petuska.kmdc.core
22

33
import androidx.compose.runtime.Composable
4+
import org.jetbrains.compose.web.attributes.AttrsBuilder
45
import org.w3c.dom.Element
6+
import org.w3c.dom.HTMLElement
7+
import org.w3c.dom.events.Event
58

69
@JsName("require")
710
public external fun requireJsModule(module: String): dynamic
@@ -15,6 +18,14 @@ public annotation class MDCAttrsDsl
1518
public typealias Builder<T> = T.() -> Unit
1619
public typealias ComposableBuilder<T> = @Composable Builder<T>
1720

21+
public external interface Destroyable {
22+
public fun destroy()
23+
}
24+
25+
public abstract external class MDCEvent<T> : Event {
26+
public var detail: T
27+
}
28+
1829
public data class Wrapper<T>(val value: T)
1930

2031
public fun <T> T.wrap(): Wrapper<T> = Wrapper(this)
@@ -29,3 +40,18 @@ public fun <T> Element.mdc(action: Builder<T>? = null): T? = mdc.unsafeCast<T?>(
2940

3041
public inline fun <T : Any> jsObject(builder: Builder<T> = { }): T =
3142
js("({})").unsafeCast<T>().apply(builder)
43+
44+
@MDCAttrsDsl
45+
public fun <T> AttrsBuilder<out HTMLElement>.initialiseMDC(mdcInit: (HTMLElement) -> T, onDispose: Builder<T>? = null) {
46+
ref {
47+
it.mdc = mdcInit(it)
48+
onDispose {
49+
it.mdc(onDispose)
50+
}
51+
}
52+
}
53+
54+
@MDCAttrsDsl
55+
public fun <T : Destroyable> AttrsBuilder<out HTMLElement>.initialiseMDC(mdcInit: (HTMLElement) -> T) {
56+
initialiseMDC(mdcInit, Destroyable::destroy)
57+
}

kmdc/kmdc-icon-button/src/jsMain/kotlin/MDCIconButton.kt

Lines changed: 2 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -4,30 +4,20 @@ import androidx.compose.runtime.Composable
44
import dev.petuska.kmdc.core.Builder
55
import dev.petuska.kmdc.core.ComposableBuilder
66
import dev.petuska.kmdc.core.MDCDsl
7+
import dev.petuska.kmdc.core.initialiseMDC
78
import dev.petuska.kmdc.core.mdc
89
import dev.petuska.kmdc.ripple.MDCRipple
910
import org.jetbrains.compose.web.dom.A
1011
import org.jetbrains.compose.web.dom.AttrBuilderContext
1112
import org.jetbrains.compose.web.dom.Button
1213
import org.jetbrains.compose.web.dom.ElementScope
1314
import org.jetbrains.compose.web.dom.Span
14-
import org.w3c.dom.Element
1515
import org.w3c.dom.HTMLAnchorElement
1616
import org.w3c.dom.HTMLButtonElement
1717

1818
@JsModule("@material/icon-button/dist/mdc.icon-button.css")
1919
private external val MDCIconButtonStyle: dynamic
2020

21-
@JsModule("@material/icon-button")
22-
private external object MDCIconButtonModule {
23-
class MDCIconButtonToggle(element: Element) {
24-
companion object {
25-
fun attachTo(element: Element): MDCIconButtonToggle
26-
}
27-
fun destroy()
28-
}
29-
}
30-
3121
public data class MDCIconButtonOpts(var on: Boolean = false)
3222

3323
public class MDCIconButtonScope(scope: ElementScope<HTMLButtonElement>) : ElementScope<HTMLButtonElement> by scope
@@ -49,12 +39,7 @@ public fun MDCIconButton(
4939
Button(
5040
attrs = {
5141
classes(*listOfNotNull("mdc-icon-button", if (options.on) "mdc-icon-button--on" else null).toTypedArray())
52-
ref {
53-
it.mdc = MDCIconButtonModule.MDCIconButtonToggle.attachTo(it)
54-
onDispose {
55-
it.mdc<MDCIconButtonModule.MDCIconButtonToggle> { destroy() }
56-
}
57-
}
42+
initialiseMDC(MDCIconButtonModule.MDCIconButtonToggle::attachTo)
5843
attrs?.invoke(this)
5944
},
6045
) {
Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
package dev.petuska.kmdc.icon.button
2+
3+
import dev.petuska.kmdc.core.Destroyable
4+
import org.w3c.dom.Element
5+
6+
@JsModule("@material/icon-button")
7+
public external object MDCIconButtonModule {
8+
public class MDCIconButtonToggle(element: Element) : Destroyable {
9+
public companion object {
10+
public fun attachTo(element: Element): MDCIconButtonToggle
11+
}
12+
13+
override fun destroy()
14+
}
15+
}
Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
import util.mdcVersion
2+
3+
plugins {
4+
id("plugin.library-compose")
5+
id("plugin.publishing-mpp")
6+
}
7+
8+
kotlin {
9+
sourceSets {
10+
named("jsMain") {
11+
dependencies {
12+
api(project(":kmdc:kmdc-core"))
13+
api(project(":kmdc:kmdc-button"))
14+
api(project(":kmdc:kmdc-icon-button"))
15+
api(npm("@material/snackbar", mdcVersion))
16+
}
17+
}
18+
}
19+
}
Lines changed: 68 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,68 @@
1+
package dev.petuska.kmdc.snackbar
2+
3+
import MDCSnackbarModule
4+
import androidx.compose.runtime.Composable
5+
import dev.petuska.kmdc.core.Builder
6+
import dev.petuska.kmdc.core.ComposableBuilder
7+
import dev.petuska.kmdc.core.MDCDsl
8+
import dev.petuska.kmdc.core.initialiseMDC
9+
import dev.petuska.kmdc.core.mdc
10+
import org.jetbrains.compose.web.dom.Aside
11+
import org.jetbrains.compose.web.dom.AttrBuilderContext
12+
import org.jetbrains.compose.web.dom.Div
13+
import org.jetbrains.compose.web.dom.ElementScope
14+
import org.w3c.dom.HTMLElement
15+
16+
@JsModule("@material/snackbar/dist/mdc.snackbar.css")
17+
private external val MDCSnackbarCSS: dynamic
18+
19+
public data class MDCSnackbarOpts(
20+
var type: Type = Type.Default,
21+
var open: Boolean = false,
22+
var dismissible: Boolean = false,
23+
) {
24+
public enum class Type(public vararg val classes: String) {
25+
Default,
26+
Stacked("mdc-snackbar--stacked"),
27+
Leading("mdc-snackbar--leading"),
28+
}
29+
}
30+
31+
public class MDCSnackbarScope(scope: ElementScope<HTMLElement>) : ElementScope<HTMLElement> by scope
32+
33+
/**
34+
* [JS API](https://github.com/material-components/material-components-web/tree/v13.0.0/packages/mdc-snackbar)
35+
*/
36+
@MDCDsl
37+
@Composable
38+
public fun MDCSnackbar(
39+
opts: Builder<MDCSnackbarOpts>? = null,
40+
attrs: AttrBuilderContext<HTMLElement>? = null,
41+
content: ComposableBuilder<MDCSnackbarScope>? = null,
42+
) {
43+
val options = MDCSnackbarOpts().apply { opts?.invoke(this) }
44+
MDCSnackbarCSS
45+
Aside(attrs = {
46+
classes("mdc-snackbar", *options.type.classes)
47+
attrs?.invoke(this)
48+
initialiseMDC(MDCSnackbarModule.MDCSnackbar.Companion::attachTo)
49+
}) {
50+
DomSideEffect(options.open) {
51+
it.mdc<MDCSnackbarModule.MDCSnackbar> {
52+
if (options.open) {
53+
open()
54+
} else {
55+
close()
56+
}
57+
}
58+
}
59+
Div(
60+
attrs = {
61+
classes("mdc-snackbar__surface")
62+
attr("role", "status")
63+
attr("aria-relevant", "additions")
64+
},
65+
content = content?.let { { MDCSnackbarScope(this).it() } }
66+
)
67+
}
68+
}
Lines changed: 95 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,95 @@
1+
package dev.petuska.kmdc.snackbar
2+
3+
import androidx.compose.runtime.Composable
4+
import dev.petuska.kmdc.button.MDCButton
5+
import dev.petuska.kmdc.button.MDCButtonLabel
6+
import dev.petuska.kmdc.button.MDCButtonOpts
7+
import dev.petuska.kmdc.button.MDCButtonScope
8+
import dev.petuska.kmdc.core.Builder
9+
import dev.petuska.kmdc.core.ComposableBuilder
10+
import dev.petuska.kmdc.core.MDCDsl
11+
import dev.petuska.kmdc.icon.button.MDCIconButton
12+
import dev.petuska.kmdc.icon.button.MDCIconButtonOpts
13+
import dev.petuska.kmdc.icon.button.MDCIconButtonScope
14+
import org.jetbrains.compose.web.attributes.ButtonType
15+
import org.jetbrains.compose.web.attributes.type
16+
import org.jetbrains.compose.web.dom.AttrBuilderContext
17+
import org.jetbrains.compose.web.dom.Div
18+
import org.jetbrains.compose.web.dom.ElementScope
19+
import org.w3c.dom.HTMLButtonElement
20+
import org.w3c.dom.HTMLDivElement
21+
22+
public class MDCSnackbarActionsScope(scope: ElementScope<HTMLDivElement>) : ElementScope<HTMLDivElement> by scope
23+
24+
/**
25+
* [JS API](https://github.com/material-components/material-components-web/tree/v13.0.0/packages/mdc-snackbar)
26+
*/
27+
@MDCDsl
28+
@Composable
29+
public fun MDCSnackbarScope.MDCSnackbarActions(
30+
attrs: AttrBuilderContext<HTMLDivElement>? = null,
31+
content: ComposableBuilder<MDCSnackbarActionsScope>? = null,
32+
) {
33+
Div(
34+
attrs = {
35+
classes("mdc-snackbar__actions")
36+
attr("aria-atomic", "true")
37+
attrs?.invoke(this)
38+
},
39+
content = content?.let { { MDCSnackbarActionsScope(this).it() } }
40+
)
41+
}
42+
43+
/**
44+
* [JS API](https://github.com/material-components/material-components-web/tree/v13.0.0/packages/mdc-snackbar)
45+
*/
46+
@MDCDsl
47+
@Composable
48+
public fun MDCSnackbarActionsScope.MDCSnackbarAction(
49+
opts: Builder<MDCButtonOpts>? = null,
50+
attrs: AttrBuilderContext<HTMLButtonElement>? = null,
51+
content: ComposableBuilder<MDCButtonScope>? = null,
52+
) {
53+
MDCButton(
54+
opts = opts,
55+
attrs = {
56+
classes("mdc-snackbar__action")
57+
type(ButtonType.Button)
58+
attrs?.invoke(this)
59+
},
60+
content = content,
61+
)
62+
}
63+
64+
/**
65+
* [JS API](https://github.com/material-components/material-components-web/tree/v13.0.0/packages/mdc-snackbar)
66+
*/
67+
@MDCDsl
68+
@Composable
69+
public fun MDCSnackbarActionsScope.MDCSnackbarAction(
70+
text: String,
71+
opts: Builder<MDCButtonOpts>? = null,
72+
attrs: AttrBuilderContext<HTMLButtonElement>? = null,
73+
) {
74+
MDCSnackbarAction(opts, attrs) { MDCButtonLabel(text) }
75+
}
76+
77+
/**
78+
* [JS API](https://github.com/material-components/material-components-web/tree/v13.0.0/packages/mdc-snackbar)
79+
*/
80+
@MDCDsl
81+
@Composable
82+
public fun MDCSnackbarActionsScope.MDCSnackbarDismiss(
83+
opts: Builder<MDCIconButtonOpts>? = null,
84+
attrs: AttrBuilderContext<HTMLButtonElement>? = null,
85+
content: ComposableBuilder<MDCIconButtonScope>? = null,
86+
) {
87+
MDCIconButton(
88+
opts = opts,
89+
attrs = {
90+
classes("mdc-snackbar__dismiss")
91+
attrs?.invoke(this)
92+
},
93+
content = content,
94+
)
95+
}
Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,40 @@
1+
package dev.petuska.kmdc.snackbar
2+
3+
import androidx.compose.runtime.Composable
4+
import dev.petuska.kmdc.core.MDCDsl
5+
import org.jetbrains.compose.web.dom.AttrBuilderContext
6+
import org.jetbrains.compose.web.dom.ContentBuilder
7+
import org.jetbrains.compose.web.dom.Div
8+
import org.jetbrains.compose.web.dom.Text
9+
import org.w3c.dom.HTMLDivElement
10+
11+
/**
12+
* [JS API](https://github.com/material-components/material-components-web/tree/v13.0.0/packages/mdc-snackbar)
13+
*/
14+
@MDCDsl
15+
@Composable
16+
public fun MDCSnackbarScope.MDCSnackbarLabel(
17+
attrs: AttrBuilderContext<HTMLDivElement>? = null,
18+
content: ContentBuilder<HTMLDivElement>? = null,
19+
) {
20+
Div(
21+
attrs = {
22+
classes("mdc-snackbar__label")
23+
attr("aria-atomic", "false")
24+
attrs?.invoke(this)
25+
},
26+
content = content
27+
)
28+
}
29+
30+
/**
31+
* [JS API](https://github.com/material-components/material-components-web/tree/v13.0.0/packages/mdc-snackbar)
32+
*/
33+
@MDCDsl
34+
@Composable
35+
public fun MDCSnackbarScope.MDCSnackbarLabel(
36+
text: String,
37+
attrs: AttrBuilderContext<HTMLDivElement>? = null,
38+
) {
39+
MDCSnackbarLabel(attrs) { Text(text) }
40+
}
Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,38 @@
1+
import dev.petuska.kmdc.core.Destroyable
2+
import dev.petuska.kmdc.core.MDCEvent
3+
import org.w3c.dom.Element
4+
5+
@JsModule("@material/snackbar")
6+
public external object MDCSnackbarModule {
7+
public class MDCSnackbar(element: Element) : Destroyable {
8+
public companion object {
9+
public fun attachTo(element: Element): MDCSnackbar
10+
}
11+
12+
public fun initialize(
13+
segmentFactory: (
14+
el: Element,
15+
foundation: dynamic
16+
) -> (() -> (ariaEl: Element, labelEl: Element?) -> Unit) = definedExternally
17+
)
18+
19+
public fun initialSyncWithDOM()
20+
public override fun destroy()
21+
public fun open()
22+
public fun close(reason: String = definedExternally)
23+
public fun getDefaultFoundation(): dynamic
24+
public var timeoutMs: Number
25+
public var closeOnEscape: Boolean
26+
public val isOpen: Boolean
27+
public var labelText: String
28+
public var actionButtonText: String
29+
}
30+
31+
public interface MDCSnackbarOpenEventDetail
32+
public interface MDCSnackbarCloseEventDetail {
33+
public val reason: String?
34+
}
35+
36+
public class MDCSnackbarOpenEvent : MDCEvent<MDCSnackbarOpenEventDetail>
37+
public class MDCSnackbarCloseEvent : MDCEvent<MDCSnackbarCloseEventDetail>
38+
}

0 commit comments

Comments
 (0)