Skip to content

Commit

Permalink
#24 kmdc-snackbar module (#59)
Browse files Browse the repository at this point in the history
  • Loading branch information
mpetuska committed Dec 28, 2021
1 parent ac4c355 commit b9f5601
Show file tree
Hide file tree
Showing 14 changed files with 458 additions and 19 deletions.
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@
- [x] mdc-radio
- [x] mdc-tooltip
- [x] mdc-segmented-button
- [x] mdc-snackbar
- [x] material-icons

# v0.0.1
Expand Down
4 changes: 2 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -71,7 +71,7 @@ fun Sample() {

### Progress

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

- [ ] mdc-animation (SASS)
- [x] mdc-auto-init (won't wrap)
Expand Down Expand Up @@ -109,7 +109,7 @@ Here's a tracker list of currently completed *material-components-web* modules (
- [ ] mdc-select
- [ ] mdc-shape (SASS)
- [ ] mdc-slider
- [ ] mdc-snackbar
- [x] mdc-snackbar
- [ ] mdc-switch
- [ ] mdc-tab-bar
- [ ] mdc-tab-indicator
Expand Down
26 changes: 26 additions & 0 deletions kmdc/kmdc-core/src/jsMain/kotlin/util.kt
Original file line number Diff line number Diff line change
@@ -1,7 +1,10 @@
package dev.petuska.kmdc.core

import androidx.compose.runtime.Composable
import org.jetbrains.compose.web.attributes.AttrsBuilder
import org.w3c.dom.Element
import org.w3c.dom.HTMLElement
import org.w3c.dom.events.Event

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

public external interface Destroyable {
public fun destroy()
}

public abstract external class MDCEvent<T> : Event {
public var detail: T
}

public data class Wrapper<T>(val value: T)

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

public inline fun <T : Any> jsObject(builder: Builder<T> = { }): T =
js("({})").unsafeCast<T>().apply(builder)

@MDCAttrsDsl
public fun <T> AttrsBuilder<out HTMLElement>.initialiseMDC(mdcInit: (HTMLElement) -> T, onDispose: Builder<T>? = null) {
ref {
it.mdc = mdcInit(it)
onDispose {
it.mdc(onDispose)
}
}
}

@MDCAttrsDsl
public fun <T : Destroyable> AttrsBuilder<out HTMLElement>.initialiseMDC(mdcInit: (HTMLElement) -> T) {
initialiseMDC(mdcInit, Destroyable::destroy)
}
19 changes: 2 additions & 17 deletions kmdc/kmdc-icon-button/src/jsMain/kotlin/MDCIconButton.kt
Original file line number Diff line number Diff line change
Expand Up @@ -4,30 +4,20 @@ import androidx.compose.runtime.Composable
import dev.petuska.kmdc.core.Builder
import dev.petuska.kmdc.core.ComposableBuilder
import dev.petuska.kmdc.core.MDCDsl
import dev.petuska.kmdc.core.initialiseMDC
import dev.petuska.kmdc.core.mdc
import dev.petuska.kmdc.ripple.MDCRipple
import org.jetbrains.compose.web.dom.A
import org.jetbrains.compose.web.dom.AttrBuilderContext
import org.jetbrains.compose.web.dom.Button
import org.jetbrains.compose.web.dom.ElementScope
import org.jetbrains.compose.web.dom.Span
import org.w3c.dom.Element
import org.w3c.dom.HTMLAnchorElement
import org.w3c.dom.HTMLButtonElement

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

@JsModule("@material/icon-button")
private external object MDCIconButtonModule {
class MDCIconButtonToggle(element: Element) {
companion object {
fun attachTo(element: Element): MDCIconButtonToggle
}
fun destroy()
}
}

public data class MDCIconButtonOpts(var on: Boolean = false)

public class MDCIconButtonScope(scope: ElementScope<HTMLButtonElement>) : ElementScope<HTMLButtonElement> by scope
Expand All @@ -49,12 +39,7 @@ public fun MDCIconButton(
Button(
attrs = {
classes(*listOfNotNull("mdc-icon-button", if (options.on) "mdc-icon-button--on" else null).toTypedArray())
ref {
it.mdc = MDCIconButtonModule.MDCIconButtonToggle.attachTo(it)
onDispose {
it.mdc<MDCIconButtonModule.MDCIconButtonToggle> { destroy() }
}
}
initialiseMDC(MDCIconButtonModule.MDCIconButtonToggle::attachTo)
attrs?.invoke(this)
},
) {
Expand Down
15 changes: 15 additions & 0 deletions kmdc/kmdc-icon-button/src/jsMain/kotlin/MDCIconButtonModule.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
package dev.petuska.kmdc.icon.button

import dev.petuska.kmdc.core.Destroyable
import org.w3c.dom.Element

@JsModule("@material/icon-button")
public external object MDCIconButtonModule {
public class MDCIconButtonToggle(element: Element) : Destroyable {
public companion object {
public fun attachTo(element: Element): MDCIconButtonToggle
}

override fun destroy()
}
}
19 changes: 19 additions & 0 deletions kmdc/kmdc-snackbar/build.gradle.kts
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
import util.mdcVersion

plugins {
id("plugin.library-compose")
id("plugin.publishing-mpp")
}

kotlin {
sourceSets {
named("jsMain") {
dependencies {
api(project(":kmdc:kmdc-core"))
api(project(":kmdc:kmdc-button"))
api(project(":kmdc:kmdc-icon-button"))
api(npm("@material/snackbar", mdcVersion))
}
}
}
}
68 changes: 68 additions & 0 deletions kmdc/kmdc-snackbar/src/jsMain/kotlin/MDCSnackbar.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,68 @@
package dev.petuska.kmdc.snackbar

import MDCSnackbarModule
import androidx.compose.runtime.Composable
import dev.petuska.kmdc.core.Builder
import dev.petuska.kmdc.core.ComposableBuilder
import dev.petuska.kmdc.core.MDCDsl
import dev.petuska.kmdc.core.initialiseMDC
import dev.petuska.kmdc.core.mdc
import org.jetbrains.compose.web.dom.Aside
import org.jetbrains.compose.web.dom.AttrBuilderContext
import org.jetbrains.compose.web.dom.Div
import org.jetbrains.compose.web.dom.ElementScope
import org.w3c.dom.HTMLElement

@JsModule("@material/snackbar/dist/mdc.snackbar.css")
private external val MDCSnackbarCSS: dynamic

public data class MDCSnackbarOpts(
var type: Type = Type.Default,
var open: Boolean = false,
var dismissible: Boolean = false,
) {
public enum class Type(public vararg val classes: String) {
Default,
Stacked("mdc-snackbar--stacked"),
Leading("mdc-snackbar--leading"),
}
}

public class MDCSnackbarScope(scope: ElementScope<HTMLElement>) : ElementScope<HTMLElement> by scope

/**
* [JS API](https://github.com/material-components/material-components-web/tree/v13.0.0/packages/mdc-snackbar)
*/
@MDCDsl
@Composable
public fun MDCSnackbar(
opts: Builder<MDCSnackbarOpts>? = null,
attrs: AttrBuilderContext<HTMLElement>? = null,
content: ComposableBuilder<MDCSnackbarScope>? = null,
) {
val options = MDCSnackbarOpts().apply { opts?.invoke(this) }
MDCSnackbarCSS
Aside(attrs = {
classes("mdc-snackbar", *options.type.classes)
attrs?.invoke(this)
initialiseMDC(MDCSnackbarModule.MDCSnackbar.Companion::attachTo)
}) {
DomSideEffect(options.open) {
it.mdc<MDCSnackbarModule.MDCSnackbar> {
if (options.open) {
open()
} else {
close()
}
}
}
Div(
attrs = {
classes("mdc-snackbar__surface")
attr("role", "status")
attr("aria-relevant", "additions")
},
content = content?.let { { MDCSnackbarScope(this).it() } }
)
}
}
95 changes: 95 additions & 0 deletions kmdc/kmdc-snackbar/src/jsMain/kotlin/MDCSnackbarActions.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,95 @@
package dev.petuska.kmdc.snackbar

import androidx.compose.runtime.Composable
import dev.petuska.kmdc.button.MDCButton
import dev.petuska.kmdc.button.MDCButtonLabel
import dev.petuska.kmdc.button.MDCButtonOpts
import dev.petuska.kmdc.button.MDCButtonScope
import dev.petuska.kmdc.core.Builder
import dev.petuska.kmdc.core.ComposableBuilder
import dev.petuska.kmdc.core.MDCDsl
import dev.petuska.kmdc.icon.button.MDCIconButton
import dev.petuska.kmdc.icon.button.MDCIconButtonOpts
import dev.petuska.kmdc.icon.button.MDCIconButtonScope
import org.jetbrains.compose.web.attributes.ButtonType
import org.jetbrains.compose.web.attributes.type
import org.jetbrains.compose.web.dom.AttrBuilderContext
import org.jetbrains.compose.web.dom.Div
import org.jetbrains.compose.web.dom.ElementScope
import org.w3c.dom.HTMLButtonElement
import org.w3c.dom.HTMLDivElement

public class MDCSnackbarActionsScope(scope: ElementScope<HTMLDivElement>) : ElementScope<HTMLDivElement> by scope

/**
* [JS API](https://github.com/material-components/material-components-web/tree/v13.0.0/packages/mdc-snackbar)
*/
@MDCDsl
@Composable
public fun MDCSnackbarScope.MDCSnackbarActions(
attrs: AttrBuilderContext<HTMLDivElement>? = null,
content: ComposableBuilder<MDCSnackbarActionsScope>? = null,
) {
Div(
attrs = {
classes("mdc-snackbar__actions")
attr("aria-atomic", "true")
attrs?.invoke(this)
},
content = content?.let { { MDCSnackbarActionsScope(this).it() } }
)
}

/**
* [JS API](https://github.com/material-components/material-components-web/tree/v13.0.0/packages/mdc-snackbar)
*/
@MDCDsl
@Composable
public fun MDCSnackbarActionsScope.MDCSnackbarAction(
opts: Builder<MDCButtonOpts>? = null,
attrs: AttrBuilderContext<HTMLButtonElement>? = null,
content: ComposableBuilder<MDCButtonScope>? = null,
) {
MDCButton(
opts = opts,
attrs = {
classes("mdc-snackbar__action")
type(ButtonType.Button)
attrs?.invoke(this)
},
content = content,
)
}

/**
* [JS API](https://github.com/material-components/material-components-web/tree/v13.0.0/packages/mdc-snackbar)
*/
@MDCDsl
@Composable
public fun MDCSnackbarActionsScope.MDCSnackbarAction(
text: String,
opts: Builder<MDCButtonOpts>? = null,
attrs: AttrBuilderContext<HTMLButtonElement>? = null,
) {
MDCSnackbarAction(opts, attrs) { MDCButtonLabel(text) }
}

/**
* [JS API](https://github.com/material-components/material-components-web/tree/v13.0.0/packages/mdc-snackbar)
*/
@MDCDsl
@Composable
public fun MDCSnackbarActionsScope.MDCSnackbarDismiss(
opts: Builder<MDCIconButtonOpts>? = null,
attrs: AttrBuilderContext<HTMLButtonElement>? = null,
content: ComposableBuilder<MDCIconButtonScope>? = null,
) {
MDCIconButton(
opts = opts,
attrs = {
classes("mdc-snackbar__dismiss")
attrs?.invoke(this)
},
content = content,
)
}
40 changes: 40 additions & 0 deletions kmdc/kmdc-snackbar/src/jsMain/kotlin/MDCSnackbarLabel.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
package dev.petuska.kmdc.snackbar

import androidx.compose.runtime.Composable
import dev.petuska.kmdc.core.MDCDsl
import org.jetbrains.compose.web.dom.AttrBuilderContext
import org.jetbrains.compose.web.dom.ContentBuilder
import org.jetbrains.compose.web.dom.Div
import org.jetbrains.compose.web.dom.Text
import org.w3c.dom.HTMLDivElement

/**
* [JS API](https://github.com/material-components/material-components-web/tree/v13.0.0/packages/mdc-snackbar)
*/
@MDCDsl
@Composable
public fun MDCSnackbarScope.MDCSnackbarLabel(
attrs: AttrBuilderContext<HTMLDivElement>? = null,
content: ContentBuilder<HTMLDivElement>? = null,
) {
Div(
attrs = {
classes("mdc-snackbar__label")
attr("aria-atomic", "false")
attrs?.invoke(this)
},
content = content
)
}

/**
* [JS API](https://github.com/material-components/material-components-web/tree/v13.0.0/packages/mdc-snackbar)
*/
@MDCDsl
@Composable
public fun MDCSnackbarScope.MDCSnackbarLabel(
text: String,
attrs: AttrBuilderContext<HTMLDivElement>? = null,
) {
MDCSnackbarLabel(attrs) { Text(text) }
}
38 changes: 38 additions & 0 deletions kmdc/kmdc-snackbar/src/jsMain/kotlin/MDCSnackbarModule.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
import dev.petuska.kmdc.core.Destroyable
import dev.petuska.kmdc.core.MDCEvent
import org.w3c.dom.Element

@JsModule("@material/snackbar")
public external object MDCSnackbarModule {
public class MDCSnackbar(element: Element) : Destroyable {
public companion object {
public fun attachTo(element: Element): MDCSnackbar
}

public fun initialize(
segmentFactory: (
el: Element,
foundation: dynamic
) -> (() -> (ariaEl: Element, labelEl: Element?) -> Unit) = definedExternally
)

public fun initialSyncWithDOM()
public override fun destroy()
public fun open()
public fun close(reason: String = definedExternally)
public fun getDefaultFoundation(): dynamic
public var timeoutMs: Number
public var closeOnEscape: Boolean
public val isOpen: Boolean
public var labelText: String
public var actionButtonText: String
}

public interface MDCSnackbarOpenEventDetail
public interface MDCSnackbarCloseEventDetail {
public val reason: String?
}

public class MDCSnackbarOpenEvent : MDCEvent<MDCSnackbarOpenEventDetail>
public class MDCSnackbarCloseEvent : MDCEvent<MDCSnackbarCloseEventDetail>
}
Loading

0 comments on commit b9f5601

Please sign in to comment.