Skip to content

Commit

Permalink
Rename classes utility function to joinClasses in order to reduce…
Browse files Browse the repository at this point in the history
… naming confusion (#859)

This PR introduces the `joinClassNames` function as a replacement for `classes`

Changes include:
- Add `joinClasses` as a drop-in replacement for the `classes` function
- Deprecate `classes` function
- Migrate existing code towards the new `joinClasses` function
  • Loading branch information
haukesomm committed Apr 22, 2024
1 parent e7d2424 commit b91675e
Show file tree
Hide file tree
Showing 15 changed files with 59 additions and 26 deletions.
35 changes: 33 additions & 2 deletions core/src/jsMain/kotlin/dev/fritz2/core/foundation.kt
Expand Up @@ -119,8 +119,39 @@ fun addGlobalStyles(css: List<String>) {
* Joins all given [classes] strings to one html-class-attribute [String]
* by filtering all out which are null or blank.
*/
fun classes(vararg classes: String?): String =
classes.filter { !it.isNullOrBlank() }.joinToString(" ")
@Deprecated("Use joinClassNames instead.", ReplaceWith("joinClasses(*classes)"))
fun classes(vararg classes: String?): String = joinClasses(*classes)

/**
* Joins all given [classes] strings to one html-class-attribute [String].
* Individual Strings that are null or blank are filtered out.
*
* __Examples__
*
* ```
* val classes = joinClasses(
* "class1",
* null,
* "class2",
* ""
* )
* println(classes) // prints "class1 class2"
* ```
*
* Using this function, it is also possible to conditionally construct classes strings without having
* to do dangerous string concatenation:
*
* ```
* val classes = joinClasses(
* "class1",
* "class2".takeIf { it.length > 10 }
* )
*
* println(classes) // prints "class1"
* ```
*/
fun joinClasses(vararg classes: String?): String =
classes.filterNot(String?::isNullOrBlank).joinToString(separator = " ")

/**
* Helper function to call a native js function with concrete return type [T]
Expand Down
6 changes: 4 additions & 2 deletions core/src/jsMain/kotlin/dev/fritz2/core/tags.kt
Expand Up @@ -376,7 +376,9 @@ open class HtmlTag<out E : Element>(
private val classesStateFlow by lazy {
MutableStateFlow<List<StateFlow<String>>>(listOfNotNull(baseClass?.let { MutableStateFlow(it) }))
.also { classesFlowList ->
attr("class", classesFlowList.flatMapLatest { styleFlows -> combine(styleFlows) { classes(*it) } })
attr("class", classesFlowList.flatMapLatest { styleFlows ->
combine(styleFlows) { joinClasses(*it) }
})
}
}

Expand All @@ -386,7 +388,7 @@ open class HtmlTag<out E : Element>(
* This function is used to create the initial class name values to be applied immediately
* to the domnode.
*/
private fun buildClasses() = classes(*classesStateFlow.value.map { it.value }.toTypedArray())
private fun buildClasses() = joinClasses(*classesStateFlow.value.map { it.value }.toTypedArray())

override fun className(value: String) {
classesStateFlow.value += MutableStateFlow(value)
Expand Down
10 changes: 5 additions & 5 deletions core/src/jsMain/kotlin/dev/fritz2/core/transitions.kt
Expand Up @@ -60,12 +60,12 @@ class Transition(
val transition = payload.unsafeCast<Transition?>()
if (transition?.enter != null) {
val classes = target.domNode.getAttribute("class").orEmpty()
target.domNode.setAttribute("class", classes(classes, transition.enterStart))
target.domNode.setAttribute("class", joinClasses(classes, transition.enterStart))
kotlinx.browser.window.awaitAnimationFrame()
kotlinx.browser.window.awaitAnimationFrame()
target.domNode.setAttribute(
"class",
classes(classes, transition.enter, transition.enterEnd)
joinClasses(classes, transition.enter, transition.enterEnd)
)
target.waitForAnimation()
target.domNode.setAttribute("class", classes)
Expand Down Expand Up @@ -144,14 +144,14 @@ fun Tag<HTMLElement>.transition(on: Flow<Boolean>, transition: Transition) {
emit(transition.enterStart.orEmpty())
kotlinx.browser.window.awaitAnimationFrame()
kotlinx.browser.window.awaitAnimationFrame()
emit(classes(transition.enter, transition.enterEnd))
emit(joinClasses(transition.enter, transition.enterEnd))
waitForAnimation()
emit("")
} else {
emit(classes(transition.leaveStart))
emit(joinClasses(transition.leaveStart))
kotlinx.browser.window.awaitAnimationFrame()
kotlinx.browser.window.awaitAnimationFrame()
emit(classes(transition.leave, transition.leaveEnd))
emit(joinClasses(transition.leave, transition.leaveEnd))
waitForAnimation()
emit("")
}
Expand Down
Expand Up @@ -61,7 +61,7 @@ fun main() {
div("row") {
gameStore.field.renderEach { cell ->
div(
classes(
joinClasses(
"col-4 border d-flex justify-content-center align-items-center",
if (cell.isInWinningGroup) "bg-success" else "bg-white"
)
Expand Down
Expand Up @@ -247,7 +247,7 @@ fun RenderContext.gridListDemo(amount: Int) {
tag = RenderContext::li
) {
className(selected.combine(active) { sel, act ->
classes(
joinClasses(
if (act) "ring-4 ring-primary-600" else "",
if (sel) "bg-primary-700 text-primary-100" else "bg-primary-200 text-primary-900"
)
Expand Down
Expand Up @@ -23,13 +23,13 @@ fun RenderContext.inputFieldDemo() {
| disabled:opacity-50""".trimMargin()
) {
className(value.hasError.map {
if (it) classes(
if (it) joinClasses(
"""border border-error-600
| text-error-800 placeholder:text-error-400
| hover:border-error-800
| focus:outline-none focus:ring-4 focus:ring-error-600 focus:border-error-800""".trimMargin()
)
else classes(
else joinClasses(
"""border border-primary-600
| text-primary-800 placeholder:text-slate-400
| hover:border-primary-800
Expand Down
@@ -1,7 +1,7 @@
package dev.fritz2.headlessdemo.components

import dev.fritz2.core.RenderContext
import dev.fritz2.core.classes
import dev.fritz2.core.joinClasses
import dev.fritz2.core.placeholder
import dev.fritz2.core.storeOf
import dev.fritz2.headless.components.textArea
Expand All @@ -28,13 +28,13 @@ fun RenderContext.textAreaDemo() {
| disabled:opacity-50""".trimMargin()
) {
className(value.hasError.map {
if (it) classes(
if (it) joinClasses(
"""border border-error-600
| text-error-800 placeholder:text-error-400
| hover:border-error-800
| focus:outline-none focus:ring-4 focus:ring-error-600 focus:border-error-800""".trimMargin()
)
else classes(
else joinClasses(
"""border border-primary-600
| text-primary-800 placeholder:text-slate-400
| hover:border-primary-800
Expand Down
Expand Up @@ -463,7 +463,7 @@ class DataCollection<T, C : HTMLElement>(tag: Tag<C>) : Tag<C> by tag {

return tag(
this,
if (selection.isSet) classes(classes, "cursor-pointer") else classes,
if (selection.isSet) joinClasses(classes, "cursor-pointer") else classes,
itemId,
scope
) {
Expand Down
Expand Up @@ -425,7 +425,7 @@ fun <T, C : HTMLElement> RenderContext.listbox(
initialize: Listbox<T, C>.() -> Unit
): Tag<C> {
addComponentStructureInfo(Listbox.COMPONENT_NAME, this@listbox.scope, this)
return tag(this, classes(classes, "relative"), id, scope) {
return tag(this, joinClasses(classes, "relative"), id, scope) {
Listbox<T, C>(this, id).run {
initialize(this)
render()
Expand Down
Expand Up @@ -327,7 +327,7 @@ fun <C : HTMLElement> RenderContext.menu(
initialize: Menu<C>.() -> Unit
): Tag<C> {
addComponentStructureInfo("menu", this@menu.scope, this)
return tag(this, classes(classes, "relative"), id, scope) {
return tag(this, joinClasses(classes, "relative"), id, scope) {
Menu(this, id).run {
initialize(this)
render()
Expand Down
Expand Up @@ -151,7 +151,7 @@ fun <C : HTMLElement> RenderContext.popOver(
initialize: PopOver<C>.() -> Unit
): Tag<C> {
addComponentStructureInfo("popOver", this@popOver.scope, this)
return tag(this, classes(classes, PopUpPanel.POPUP_RELATIVE), id, scope) {
return tag(this, joinClasses(classes, PopUpPanel.POPUP_RELATIVE), id, scope) {
PopOver(this, id).run {
initialize(this)
render()
Expand Down
Expand Up @@ -267,7 +267,7 @@ abstract class PopUpPanel<C : HTMLElement>(
* @param offset the distance between the reference element and the panel in pixels. Defaults to `5`
*/
fun arrow(size: String = "popup-arrow-default", offset: Int = 5) {
div(classes(size, "popup-arrow")) {
div(joinClasses(size, "popup-arrow")) {
arrow = this
addMiddleware(offset(offset))
addMiddleware(arrow { element = domNode })
Expand Down
4 changes: 2 additions & 2 deletions www/src/pages/headless/radioGroup.md
Expand Up @@ -109,8 +109,8 @@ radioGroup<HTMLFieldSetElement, Plan?>(tag = RenderContext::fieldset) {
radioGroupOptionToggle {
// combine `selected` and `active`-Flow with `className` to react to state changes
className(selected.combine(active) { sel, act ->
// use `classes` to attach both styling results
classes(
// use `joinClasses` to attach both styling results
joinClasses(
if (sel) "bg-indigo-200" else "bg-white",
if (act) "ring-2 ring-indigo-500 border-transparent" else "border-gray-300"
)
Expand Down
4 changes: 2 additions & 2 deletions www/src/pages/headless/tabGroup.md
Expand Up @@ -114,8 +114,8 @@ tabGroup {
tab {
// combine `selected` and `active`-Flow with `className` to react to state changes
className(selected.combine(active) { sel, act ->
// use `classes` to attach both styling results
classes(
// use `joinClasses` to attach both styling results
joinClasses(
if (sel == index) "bg-primary-800 text-white shadow-md"
else "text-primary-100 hover:bg-primary-900/[0.12]",
if (act) "ring-2 ring-white border-transparent"
Expand Down
2 changes: 1 addition & 1 deletion www/src/pages/headless/toast.md
Expand Up @@ -104,7 +104,7 @@ toast("default") {

button {
icon(
classes = "w-4 h-4 text-primary-900",
joinClasses = "w-4 h-4 text-primary-900",
content = HeroIcons.x
)
clicks handledBy close // call close handler
Expand Down

0 comments on commit b91675e

Please sign in to comment.