8000 kmdc-select (#22) by Kiryushin-Andrey · Pull Request #80 · mpetuska/kmdc · GitHub
[go: up one dir, main page]
More Web Proxy on the site http://driver.im/
Skip to content

kmdc-select (#22) #80

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

Merged
merged 7 commits into from
Feb 2, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions kmdc/kmdc-menu-surface/src/jsMain/kotlin/MDCMenuSurface.kt
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ private external val MDCMenuSurfaceStyle: dynamic

public data class MDCMenuSurfaceOpts(
public var fixed: Boolean = false,
public var fullwidth: Boolean = false
)

public open class MDCMenuSurfaceAttrsScope : AttrsBuilder<HTMLDivElement>()
Expand All @@ -34,6 +35,7 @@ public fun MDCMenuSurface(
attrs = {
classes("mdc-menu-surface")
if (options.fixed) classes("mdc-menu-surface--fixed")
if (options.fullwidth) classes("mdc-menu-surface--fullwidth")
initialiseMDC(MDCMenuSurfaceModule.MDCMenuSurface::attachTo)
attrs?.invoke(this.unsafeCast<MDCMenuSurfaceAttrsScope>())
},
Expand Down
23 changes: 23 additions & 0 deletions kmdc/kmdc-select/build.gradle.kts
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
import util.mdcVersion

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

description = "Compose Multiplatform Kotlin/JS wrappers for @material/select"

kotlin {
sourceSets {
commonMain
jsMain {
dependencies {
api(project(":kmdc:kmdc-core"))
api(project(":kmdc:kmdc-list"))
api(project(":kmdc:kmdc-menu-surface"))
api(npm("@material/select", mdcVersion))
api(compose.web.svg)
}
}
}
}
155 changes: 155 additions & 0 deletions kmdc/kmdc-select/src/jsMain/kotlin/MDCSelect.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,155 @@
package dev.petuska.kmdc.select

import androidx.compose.runtime.Composable
import dev.petuska.kmdc.core.Builder
import dev.petuska.kmdc.core.MDCDsl
import dev.petuska.kmdc.core.aria
import dev.petuska.kmdc.core.initialiseMDC
import dev.petuska.kmdc.core.mdc
import dev.petuska.kmdc.core.rememberUniqueDomElementId
import dev.petuska.kmdc.core.role
import dev.petuska.kmdc.list.MDCList
import dev.petuska.kmdc.list.MDCListItem
import dev.petuska.kmdc.list.MDCListItemGraphic
import dev.petuska.kmdc.list.MDCListItemText
import dev.petuska.kmdc.menu.surface.MDCMenuSurface
import org.jetbrains.compose.web.attributes.AttrsBuilder
import org.jetbrains.compose.web.attributes.name
import org.jetbrains.compose.web.dom.Div
import org.jetbrains.compose.web.dom.ElementScope
import org.jetbrains.compose.web.dom.HiddenInput
import org.jetbrains.compose.web.dom.Text
import org.w3c.dom.HTMLDivElement
import org.w3c.dom.HTMLSpanElement

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

public data class MDCSelectOpts<T>(
var type: Type = Type.Filled,
var value: T? = null,
var itemValue: T.() -> String = { toString() },
var itemDisabled: T.() -> Boolean = { false },
var label: String? = null,
var required: Boolean = false,
var disabled: Boolean = false,
var hiddenInputName: String? = null,
var helperText: String? = null,
var helperTextType: HelperTextType = HelperTextType.Default,
var leadingIcon: String? = null,
var leadingIconClickable: Boolean = false,
var leadingIconClasses: List<String> = listOf("material-icons")
) {
public enum class Type(public val klass: String) {
Outlined("mdc-select--outlined"),
Filled("mdc-select--filled")
}

public enum class HelperTextType(public vararg val classes: String) {
Default,
Validation("mdc-select-helper-text--validation-msg"),
PersistentValidation("mdc-select-helper-text--validation-msg", "mdc-select-helper-text--validation-msg-persistent")
}
}

public class MDCSelectAttrsScope<T> private constructor() : AttrsBuilder<HTMLDivElement>()

/**
* [JS API](https://github.com/material-components/material-components-web/tree/v13.0.0/packages/mdc-select)
*/
@MDCDsl
@Composable
public fun <T> MDCSelect(
items: List<T>,
opts: Builder<MDCSelectOpts<T>>? = null,
attrs: Builder<MDCSelectAttrsScope<T>>? = null,
renderItem: @Composable ElementScope<HTMLSpanElement>.(T) -> Unit = { Text(it.toString()) }
) {
MDCSelectCSS
val labelId = rememberUniqueDomElementId()
val selectedTextId = rememberUniqueDomElementId()
val helperTextId = rememberUniqueDomElementId()
val options = MDCSelectOpts<T>().apply { opts?.invoke(this) }
val hasLeadingIcon = options.leadingIcon != null

fun T.itemValue() = with(options) { itemValue() }
fun T.itemDisabled() = with(options) { itemDisabled() }

Div(
attrs = {
with(options) {
classes("mdc-select", type.klass)
if (label == null) classes("mdc-select--no-label")
if (required) classes("mdc-select--required")
if (disabled) classes("mdc-select--disabled")
if (required && value?.itemValue().isNullOrBlank()) classes("mdc-select--invalid")
if (hasLeadingIcon) classes("mdc-select--with-leading-icon")
if (helperText != null) {
aria("controls", helperTextId)
aria("describedby", helperTextId)
}
}
initialiseMDC(
mdcInit = { MDCSelectModule.MDCSelect.attachTo<T>(it) },
postInit = { _, mdc -> mdc.items = items }
)
attrs?.invoke(this.unsafeCast<MDCSelectAttrsScope<T>>())
}
) {

DomSideEffect(options.required) {
it.mdc<MDCSelectModule.MDCSelect<T>> { required = options.required }
}
DomSideEffect(options.disabled) {
it.mdc<MDCSelectModule.MDCSelect<T>> { disabled = options.disabled }
}
DomSideEffect(options.value) {
it.mdc<MDCSelectModule.MDCSelect<T>> { value = options.value?.itemValue() }
}

options.hiddenInputName?.let {
HiddenInput { name(it) }
}

MDCSelectAnchor(labelId, options, selectedTextId, items, renderItem)

MDCMenuSurface(
opts = { fullwidth = true },
attrs = {
classes("mdc-select__menu", "mdc-menu")
}
) {
MDCList(
opts = { singleSelection = true },
attrs = {
options.label?.let { aria("label", it) }
}
) {
items.forEach { item ->
MDCListItem(
opts = {
this.selected = item == options.value
this.disabled = item.itemDisabled()
},
attrs = {
attr("data-value", item.itemValue())
aria("selected", (item == options.value).toString())
if (item.itemDisabled()) aria("disabled", "true")
role("option")
}
) {
if (hasLeadingIcon) {
MDCListItemGraphic()
}
MDCListItemText {
renderItem(item)
}
}
}
}
}
}
options.helperText?.let { helperText ->
MDCSelectHelperText(helperTextId, helperText, options.helperTextType)
}
}
78 changes: 78 additions & 0 deletions kmdc/kmdc-select/src/jsMain/kotlin/MDCSelectAnchor.kt
AE20
Original file line number Diff line number Diff line change
@@ -0,0 +1,78 @@
package dev.petuska.kmdc.select

import androidx.compose.runtime.Composable
import dev.petuska.kmdc.core.aria
import dev.petuska.kmdc.core.role
import org.jetbrains.compose.web.dom.Div
import org.jetbrains.compose.web.dom.ElementScope
import org.jetbrains.compose.web.dom.I
import org.jetbrains.compose.web.dom.Span
import org.jetbrains.compose.web.dom.Text
import org.w3c.dom.HTMLSpanElement

@Composable
internal fun <T> MDCSelectAnchor(
labelId: String,
options: MDCSelectOpts<T>,
selectedTextId: String,
items: List<T>,
renderItem: @Composable ElementScope<HTMLSpanElement>.(T) -> Unit
) {
fun T.itemValue() = with(options) { itemValue() }

Div(
attrs = {
classes("mdc-select__anchor")
role("button")
aria("haspopup", "listbox")
aria("expanded", "false")
aria("labelledby", labelId)
}
) {

MDCSelectLabel(options, labelId)

options.leadingIcon?.let { icon ->
MDCSelectLeadingIcon(options, icon)
}

Span(
attrs = {
classes("mdc-select__selected-text-container")
}
) {
Span(
attrs = {
classes("mdc-select__selected-text")
id(selectedTextId)
}
) {
options.value?.itemValue()?.let { selectedValue ->
items.firstOrNull { it.itemValue() == selectedValue }?.let {
renderItem(it)
}
}
}
}

MDCSelectDownArrowIcon()

Span(attrs = { classes("mdc-line-ripple") })
}
}

@Composable
private fun <T> MDCSelectLeadingIcon(options: MDCSelectOpts<T>, icon: String) {
I(
attrs = {
classes("mdc-select__icon")
if (options.leadingIconClickable) {
tabIndex(0)
role("button")
}
classes(*options.leadingIconClasses.toTypedArray())
}
) {
Text(icon)
}
}
43 changes: 43 additions & 0 deletions kmdc/kmdc-select/src/jsMain/kotlin/MDCSelectDropdownIcon.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
package dev.petuska.kmdc.select

import androidx.compose.runtime.Composable
import org.jetbrains.compose.web.ExperimentalComposeWebSvgApi
import org.jetbrains.compose.web.dom.Span
import org.jetbrains.compose.web.svg.Polygon
import org.jetbrains.compose.web.svg.Svg

/**
* [JS API](https://github.com/material-components/material-components-web/tree/v13.0.0/packages/mdc-select)
*/
@OptIn(ExperimentalComposeWebSvgApi::class)
@Composable
internal fun MDCSelectDownArrowIcon() {
Span(
attrs = { classes("mdc-select__dropdown-icon") }
) {
Svg(
viewBox = "7 10 10 5",
attrs = {
attr("focusable", "false")
classes("mdc-select__dropdown-icon-graphic")
}
) {
Polygon(
7, 10, 12, 15, 17, 10,
attrs = {
classes("mdc-select__dropdown-icon-inactive")
attr("stroke", "none")
attr("fill-rule", "evenodd")
}
)
Polygon(
7, 15, 12, 10, 17, 15,
attrs = {
classes("mdc-select__dropdown-icon-active")
attr("stroke", "none")
attr("fill-rule", "evenodd")
}
)
}
}
}
20 changes: 20 additions & 0 deletions kmdc/kmdc-select/src/jsMain/kotlin/MDCSelectHelperText.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
package dev.petuska.kmdc.select

import androidx.compose.runtime.Composable
import dev.petuska.kmdc.core.initialiseMDC
import org.jetbrains.compose.web.dom.P
import org.jetbrains.compose.web.dom.Text

@Composable
internal fun MDCSelectHelperText(id: String, text: String, type: MDCSelectOpts.HelperTextType) {
P(
attrs = {
id(id)
classes("mdc-select-helper-text")
classes(*type.classes)
initialiseMDC(MDCSelectModule.MDCSelectHelperText.Companion::attachTo)
}
) {
Text(text)
}
}
51 changes: 51 additions & 0 deletions kmdc/kmdc-select/src/jsMain/kotlin/MDCSelectLabel.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
package dev.petuska.kmdc.select

import androidx.compose.runtime.Composable
import org.jetbrains.compose.web.dom.Span
import org.jetbrains.compose.web.dom.Text

/**
* [JS API](https://github.com/material-components/material-components-web/tree/v13.0.0/packages/mdc-select)
*/
@Composable
internal fun <T> MDCSelectLabel(
options: MDCSelectOpts<T>,
labelId: String
) {
when (options.type) {
MDCSelectOpts.Type.Filled -> {
Span(attrs = { classes("mdc-select__ripple") })
options.label?.let {
MDCSelectFloatingLabel(labelId, it)
}
}
MDCSelectOpts.Type.Outlined -> {
Span(
attrs = { classes("mdc-notched-outline") }
) {
Span(attrs = { classes("mdc-notched-outline__leading") })
options.label?.let {
Span(attrs = { classes("mdc-notched-outline__notch") }) {
MDCSelectFloatingLabel(labelId, it)
}
}
Span(attrs = { classes("mdc-notched-outline__trailing") })
}
}
}
}

/**
* [JS API](https://github.com/material-components/material-components-web/tree/v13.0.0/packages/mdc-select)
*/
@Composable
private fun MDCSelectFloatingLabel(id: String, label: String) {
Span(
attrs = {
classes("mdc-floating-label", "mdc-floating-label--float-above")
id(id)
}
) {
Text(label)
}
}
Loading
0