Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How to use SVG with kotlin-react

Tags:

reactjs

kotlin

Hi could someone help me how use SVGs with Kotlin React.

I'd like to achieve similar thing as with following example using react and javascript:

const MyComponent = ({radius, color}) => (
  <svg width={radius * 2} height={radius * 2}>
    <circle cx={radius} cy={radius} r={radius} fill=  {color}/>
  </svg>
)

Unfortunately I haven't been able to find by myself any example or sufficient documentation.

Using Kotlin i don't know what language features I could use to achieve the same. Could anyone help me to fill following snippet using Kotlin?

fun RBuilder.MyComponent(radius: Int, color: String) {
  svg {
     ...
  }
}

Thanks a lot.

like image 427
palasjir Avatar asked Feb 03 '18 10:02

palasjir


4 Answers

As of this writing Kotlin does not appear to have a library for svg similiar to kotlinx.html. That being said, it is possible to create arbitrary xml in kotlin-react by reverse engineering the way kotlin-react and kotlinx.html interact. Or just use base react functions.

Method 1: Using kotlinx.html

import kotlinx.html.HTMLTag 
import react.*
import react.dom.*

// a custom tag builder, reuses the tag(...) function from kotlin-react and HTMLTag from kotlinx.html
inline fun RBuilder.custom(tagName: String, block: RDOMBuilder<HTMLTag>.() -> Unit): ReactElement = tag(block) {
    HTMLTag(tagName, it, mapOf(), null, true, false) // I dont know yet what the last 3 params mean... to lazy to look it up
}

// example use
inline fun RBuilder.mySVG(animTime: Double) {
    svg() {
        attrs["width"] = "800"
        attrs["height"] = "600"
        attrs["viewBox"] = "0 0 800 600"

        custom("circle") {
            attrs["cx"] = 150
            attrs["cy"] = 150
            attrs["r"] = 50.0 + sin(animTime) * 50.0
            attrs["style"] = object {
                val stroke = "black"
                val fill = "red"
            }
        }
    }
}

Method 2: Using child and createElement from react.dom

open class CircleProps (
    val cx: Int,
    val cy: Int,
    val r: Int,
    val stroke: String,
    val fill: String,
    val strokeWidth: Int
    ): RProps {
}

inline fun RBuilder.mySVG2(animTime: Double) {
svg() {
    attrs["width"] = "800"
    attrs["height"] = "600"
    attrs["viewBox"] = "0 0 800 600"

    child(createElement("circle", CircleProps(150,150, 50.0 + sin(animTime) * 50.0, "black", "blue", 3)))
}
like image 156
Martin Lütke Avatar answered Nov 20 '22 21:11

Martin Lütke


I'm not sure how to write SVG inline in the Kotlin/React file, however, JetBrain's own example shows how to load an external SVG file in Kotlin-React.

@JsModule("src/logo/react.svg")
external val reactLogo: dynamic
@JsModule("src/logo/kotlin.svg")
external val kotlinLogo: dynamic

fun RBuilder.logo(height: Int = 100) {
    div("Logo") {
        attrs.jsStyle.height = height
        img(alt = "React logo.logo", src = reactLogo, classes = "Logo-react") {}
        img(alt = "Kotlin logo.logo", src = kotlinLogo, classes = "Logo-kotlin") {}
    }
}

If you do work out how to do it inline, please post that solution back here for the community, thanks.

like image 30
Big Rich Avatar answered Nov 20 '22 23:11

Big Rich


All SVG components already declared in react.dom.svg.ReactSVG object

circle missed right now - I will add it (fast WA in issue)

path example

like image 1
Turansky Avatar answered Nov 20 '22 22:11

Turansky


You can also inline SVG files' contents directly into HTML.

  1. Add implementation(devNpm("@svgr/webpack", "6.2.1")) into your dependencies { ... } inside build.gradle[.kts].

  2. Configure webpack to make reading and minifying of SVG files possible - create any JS file inside the webpack.config.d folder in your project's root with the following contents:

config.module.rules.push({
    test: /\.svg$/,
    use: ["@svgr/webpack"]
});
  1. This step can be treated as optional - just a kind of syntax sugar. In your Kotlin sources create the following interface:
external interface SvgFile {

    @JsName("default")
    operator fun invoke(): ReactNode

}
  1. Each time you want to inline some SVG into your HTML, include the required SVG file first (arrow-right-thin.svg is located at src/main/resources/icons/arrow-right-thin.svg):
@JsModule("./icons/arrow-right-thin.svg")
@JsNonModule
private external val arrowRightThin: SvgFile

and place it where needed in the component:

val example = fc<Props> { 
    +arrowRightThin()
}

Tested with:

  • Kotlin JS plugin 1.6.10
  • React wrappers 18.0.0-pre.325-kotlin-1.6.10

Update (May 14, 2022): I think the interface described in #3 above should be changed into:

external interface SvgFile {

    @JsName("default")
    val component: ElementType<PropsWithClassName>

}

operator fun SvgFile.invoke(): ReactNode = component.create()

In this way in addition to the usage described in #4 above it also becomes possible to extend an included SVG by applying CSS classes to it:

val example = fc<Props> { 
    arrowRightThin.component {
        attrs.className = ClassName("some-class")
    }
}
like image 1
Andrei K. Avatar answered Nov 20 '22 21:11

Andrei K.