Bellow are the key concepts required to work with KMDC efficiently:
- Familiarity with compose-web.
- Familiarity with Kotlin/JS interop
- Familiarity with MDC architecture
- Familiarity with Integrating MDC Web into Frameworks
MDC component refers to the material-components-web framework component. A KMDC component is a Kotlin wrapper for an MDC component.
Before starting, here are some general pointers about MDC & KMDC architecture:
- Each MDC component lives in a separate KMDC module.
- Some MDC components depend on SCSS, which needs to be imported for it to function properly.
- Inlining CSS imports does not prevent end-users from using SCSS if they so choose.
- Most MDC components have a companion JS object, which needs to be initialised and disposed together with wrapping a composable.
- Each KMDC component has (in the following order)
- a set of custom dedicated primitive arguments (e.g.
checked
argument for MDCCheckbox.kt) - the usual compose-web attribute builders (
attrs
&content
).
- a set of custom dedicated primitive arguments (e.g.
- KMDC component options should aim to be as expressive in their naming and typing as possible to be easily discoverable in the public API
Most MDC components depend on some SCSS to function properly. To make the end-user experience smoother, KMDC auto-imports such SCSS where needed and hides this fact from the consumers.
First, you need to create a reference to appropriate SCSS module.
@JsModule("@material/checkbox/mdc-checkbox.scss") // 1
external val Style: dynamic // 2
- Tells the Kotlin compiler that the following declaration refers to a JS module and should use the JS native module resolver. This is further picked up by webpack SCSS loader to eventually inline the SCSS module into the final JS executable.
- Declares the Kotlin reference to it as
external
value withdynamic
type, since we only need to know the reference and not the type.
Once you have a reference to it, you need to make sure that DCE does not remove it. This is done by holding a static reference in a top-level composable.
@Composable
fun MDCCheckbox() {
Style
// Rest of the component logic
}
Most of the MDC components also depend on MDC JS objects that need to be initialised and disposed together with the underlying HTML component's lifecycle. In such cases, Kotlin needs to know about its MDC JS interface and types to be able to use them from compose context.
Firstly, declare the external JS module file named _module.kt
@file:JsModule("@material/list")
// 1
external class MDCList(element: Element) : MDCBaseModule.MDCComponent<dynamic> { // 2
var vertical: Boolean // 3
val typeaheadInProgress: Boolean // 4
fun layout() // 5
}
- Tells Kotlin compiler that the following declaration refers to a JS module and should use the JS native module
resolver. All declarations inside will be mapped 1:1 to
the JS module's
module.exports
or cumulative list of es6export
statements. The declarations are read from the entry file declared in npm package'spackage.json#main
field, since we do not specify further path from base module name (@material/checkbox
). - Declares an
external class
with a constructor from the external module, that extends another external class. - Declares a mutable
property
property of this class. - Declares an immutable
property
property of this class. - Declares a
method
of this class.
When you have the external declarations ready, you can start using them to implement an adapter lifecycle into your
composables. This is done via the MDCProvider
helper function inside the underlying HTML element's content
builder.
@Composable
fun MDCCheckbox(vertical: Boolean) {
Div(attrs = {
classes("mdc-list")
}) {
MDCProvider(::MDCList) {
MDCStateEffect(vertical, MDCList::vertical)
// Composable content
}
}
}
The MDC component state can be accessed and synchronised anywhere from within the MDCProvider
scope using these custom
kmdc utilities:
MDCSideEffect
-> baseline effect allowing you to run arbitrary code onMDC
component when keys changeMDCStateEffect
-> more specialised version ofMDCSideEffect
that automatically syncs up given value with the specificMDC
component property or methodlocalMDC
-> allows one to retrieve theMDC
component reference directly
- Pick an MDC component to wrap and familiarize yourself with its interface.
- Read the MDC component's README (e.g.: mdc-checkbox) to understand how its HTML works.
- Check out the MDC component's Typescript/JavaScript interface to understand how its underlying JS control works.
- Create the KMDC component. See MDCCheckbox.kt for an example.
- Create new
kmdc-
module in [../kmdc] - Create a wrapper for top-level MDC component
- If needed, declare the MDC component's SCSS module and reference it in KMDC's main composable function.
- If needed, declare a JS external module for the MDC component object and use it to initialise and synchronise with compose state
- If needed, create wrappers for your chosen module's MDC subcomponents.
- Create a new
Showcases
file in [../sandbox/src/jsMain/kotlin/showcases] to render and test your composable in action!
- Create new