main.styled.StyledComponents.kt Maven / Gradle / Ivy
package styled
import csstype.ClassName
import kotlinx.browser.window
import kotlinx.css.CssBuilder
import kotlinx.css.CssDsl
import kotlinx.css.RuleSet
import kotlinx.html.*
import kotlinx.js.jso
import org.w3c.dom.Element
import react.*
import react.dom.DOMProps
import react.dom.RDOMBuilder
import react.dom.RDOMBuilderImpl
import react.dom.render
import kotlin.js.Promise
typealias AnyTagStyledBuilder = StyledDOMBuilder
typealias AnyBuilder = AnyTagStyledBuilder.() -> Unit
typealias HTMLTagBuilder = StyledDOMBuilder.() -> Unit
typealias ABuilder = StyledDOMBuilder.() -> Unit
typealias DIVBuilder = StyledDOMBuilder.() -> Unit
typealias SPANBuilder = StyledDOMBuilder.() -> Unit
typealias INPUTBuilder = StyledDOMBuilder.() -> Unit
external interface CustomStyledProps : Props {
var css: ArrayList?
}
inline fun CustomStyledProps.forwardCss(builder: CssBuilder) {
css?.forEach { it(builder) }
}
inline fun CustomStyledProps.forwardCss(props: CustomStyledProps) {
css?.forEach { c ->
if (props.css == null) {
props.css = ArrayList()
}
props.css!!.add(c)
}
}
@CssDsl
interface StyledBuilder {
val css: CssBuilder
val type: Any
}
inline fun StyledBuilder<*>.css(handler: RuleSet) = css.handler()
interface StyledElementBuilder
: RElementBuilder
, StyledBuilder
{
fun create(): ReactElement<*>
companion object {
operator fun
invoke(
type: ElementType
,
attrs: P = jso(),
): StyledElementBuilder
= StyledElementBuilderImpl(type, attrs)
}
}
class StyledElementBuilderImpl
(
override val type: ElementType
,
attrs: P = jso(),
) : StyledElementBuilder
, RElementBuilderImpl
(attrs) {
override val css = CssBuilder(isStyledComponent = true)
override fun create() = Styled.createElement(type, css, attrs, childList)
}
@ReactDsl
interface StyledDOMBuilder : RDOMBuilder, StyledBuilder {
override val type get() = attrs.tagName
override fun create() = Styled.createElement(type, css, domProps, childList)
companion object {
operator fun invoke(factory: (TagConsumer) -> T): StyledDOMBuilder =
StyledDOMBuilderImpl(factory)
}
}
class StyledDOMBuilderImpl(factory: (TagConsumer) -> T) : StyledDOMBuilder,
RDOMBuilderImpl(factory) {
override val css = CssBuilder(isStyledComponent = true)
}
typealias StyledHandler = StyledElementBuilder
.() -> Unit
fun
styled(type: ElementType
): RBuilder.(StyledHandler
) -> Unit = { handler ->
child(with(StyledElementBuilder(type)) {
handler()
create()
})
}
inline fun CustomStyledProps.css(noinline handler: RuleSet) {
if (css == null) {
css = ArrayList()
}
css!!.add(handler)
}
@Suppress("NOTHING_TO_INLINE")
inline fun
RElementBuilder
.css(noinline handler: RuleSet) = attrs.css(handler)
/**
* @deprecated Use [keyframes] and [css] instead
*/
fun keyframesName(string: String): String {
val keyframes = keyframes(string)
val keyframesInternal = css(keyframes.rules).asDynamic()
val name = keyframes.getName()
when {
keyframesInternal is String -> injectGlobalKeyframeStyle(name, keyframesInternal)
keyframesInternal is Array -> injectGlobalKeyframeStyle(name, keyframesInternal[0])
else -> injectGlobals(keyframesInternal)
}
return keyframes.getName()
}
private fun injectGlobalKeyframeStyle(name: String, style: String) {
if (style.startsWith("@-webkit-keyframes") || style.startsWith("@keyframes")) {
injectGlobal(style)
} else {
injectGlobals(arrayOf(
"@-webkit-keyframes $name {$style}",
"@keyframes $name {$style}"
))
}
}
private fun injectGlobals(strings: Array) {
val globalStyle = devOverrideUseRef { createGlobalStyle(strings) }
Promise.resolve(Unit).then {
GlobalStyles.add(globalStyle)
}
}
private external interface GlobalStylesComponentProps : Props {
var globalStyles: List>
}
private object GlobalStyles {
private val component = fc { props ->
props.globalStyles.forEach {
child(it)
}
}
private val root by kotlin.lazy {
val element = window.document.body!!.appendChild(window.document.createElement("div")) as Element
element.setAttribute("id", "sc-global-styles")
element
}
private val styles = mutableListOf>()
fun add(globalStyle: ComponentType<*>) {
styles.add(globalStyle)
val reactElement = createElement(component, jso {
this.globalStyles = styles
})
@Suppress("DEPRECATION")
render(reactElement, root)
}
}
fun injectGlobal(css: CssBuilder) {
injectGlobal(css.toString())
}
/**
* @deprecated Use [createGlobalStyle] instead
*/
fun injectGlobal(string: String) {
val globalStyle = devOverrideUseRef { createGlobalStyle(string) }
Promise.resolve(Unit).then {
GlobalStyles.add(globalStyle)
}
}
@JsModule("react")
@JsNonModule
external object ReactModule
private fun devOverrideUseRef(action: () -> T): T {
return if (js("process.env.NODE_ENV !== 'production'")) {
// (Very) dirty hack: styled-components calls useRef() in development mode to check if a component
// has been created dynamically. We can't allow this call to happen because it breaks rendering, so
// we temporarily redefine useRef.
val useRef = ReactModule.asDynamic().useRef
ReactModule.asDynamic().useRef = {
throw Error("invalid hook call")
}
val result = action()
ReactModule.asDynamic().useRef = useRef
result
} else action()
}
/**
* @deprecated Use [createGlobalStyle] instead
*/
fun injectGlobal(handler: CssBuilder.() -> Unit) {
injectGlobal(CssBuilder(isStyledComponent = true).apply { handler() }.toString())
}
object Styled {
private val cache = mutableMapOf()
private fun wrap(type: T): T =
cache.getOrPut(type) {
devOverrideUseRef { rawStyled(type)({ it.css }) }
}
private fun buildStyledProps(css: CssBuilder, props: P): P {
val styledProps = props.unsafeCast()
if (css.rules.isNotEmpty() || css.multiRules.isNotEmpty() || css.declarations.isNotEmpty()) {
styledProps.css = css.toString()
}
if (css.classes.isNotEmpty()) {
styledProps.className = ClassName(css.classes.joinToString(separator = " "))
}
if (css.styleName.isNotEmpty()) {
styledProps.asDynamic()["data-style"] = css.styleName.joinToString(separator = " ")
}
return styledProps.unsafeCast()
}
fun createElement(
type: String,
css: CssBuilder,
props: PropsWithClassName,
children: List,
): ReactElement<*> {
val wrappedType = wrap(type)
val styledProps = buildStyledProps(css, props)
return createElement(
type = IntrinsicType(wrappedType),
props = styledProps,
children = children.toTypedArray(),
)
}
fun createElement(
type: ElementType
,
css: CssBuilder,
props: P,
children: List,
): ReactElement {
val wrappedType = wrap(type)
val styledProps = buildStyledProps(css, props)
return createElement(wrappedType, styledProps, *children.toTypedArray())
}
}
© 2015 - 2024 Weber Informatics LLC | Privacy Policy