Glow Search Box

Glow Search Box

Subscribe for Live Previews, in your browser

$3 / month

Code

import androidx.compose.animation.core.LinearEasing
import androidx.compose.animation.core.animateFloat
import androidx.compose.animation.core.infiniteRepeatable
import androidx.compose.animation.core.rememberInfiniteTransition
import androidx.compose.animation.core.tween
import androidx.compose.foundation.background
import androidx.compose.foundation.border
import androidx.compose.foundation.layout.Row
import androidx.compose.foundation.layout.Spacer
import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.layout.width
import androidx.compose.foundation.shape.CircleShape
import androidx.compose.foundation.text.BasicTextField
import androidx.compose.material.Icon
import androidx.compose.material.LocalTextStyle
import androidx.compose.material.icons.Icons
import androidx.compose.material.icons.rounded.Search
import androidx.compose.runtime.Composable
import androidx.compose.runtime.Stable
import androidx.compose.runtime.derivedStateOf
import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.remember
import androidx.compose.runtime.setValue
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.draw.dropShadow
import androidx.compose.ui.geometry.Offset
import androidx.compose.ui.graphics.Brush
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.graphics.lerp
import androidx.compose.ui.unit.dp
import theme.Blue200
import theme.Blue400
import theme.Green200
import theme.Green400
import theme.Red200
import theme.Red400
import theme.Transparent
import theme.Yellow200
import theme.Yellow400
import theme.Zinc100
import theme.Zinc200
import theme.Zinc700

@Composable
fun GlowSearchBox() {
    var text by remember { mutableStateOf("") }
    BasicTextField(
        value = text,
        onValueChange = { text = it },
        modifier = Modifier.width(300.dp),
        cursorBrush = Brush.verticalGradient(
            colors = listOf(Zinc200, Zinc200)
        ),
        textStyle = LocalTextStyle.current.copy(
            color = Zinc200,
        ),
        decorationBox = { field ->
            val rotation by rememberInfiniteTransition().animateFloat(
                initialValue = 0f,
                targetValue = 360f,
                animationSpec = infiniteRepeatable(
                    animation = tween(
                        durationMillis = 3000,
                        easing = LinearEasing,
                    )
                )
            )

            val shadowBrush by remember {
                derivedStateOf {
                    sweepGradient(
                        colors = listOf(
                            Zinc100.copy(alpha = .2f),
                            Zinc100.copy(alpha = .2f),
                            Red400,
                            Yellow400,
                            Green400,
                            Blue400,
                            Zinc100.copy(alpha = .2f),
                            Zinc100.copy(alpha = .2f),
                        ),
                        rotation = rotation,
                    )
                }
            }

            val borderBrush by remember {
                derivedStateOf {
                    sweepGradient(
                        colors = listOf(
                            Transparent,
                            Transparent,
                            Red200,
                            Yellow200,
                            Green200,
                            Blue200,
                            Transparent,
                            Transparent,
                        ),
                        rotation = rotation,
                    )
                }
            }

            Row(
                modifier = Modifier
                    .dropShadow(
                        shape = CircleShape,
                    ) {
                        alpha = .5f
                        radius = 40f
                        brush = shadowBrush
                    }
                    .dropShadow(
                        shape = CircleShape,
                    ) {
                        alpha = .2f
                        spread = 50f
                        radius = 400f
                        brush = shadowBrush
                    }
                    .background(
                        color = Zinc700,
                        shape = CircleShape
                    )
                    .border(
                        width = 1.dp,
                        brush = borderBrush,
                        shape = CircleShape,
                    )
                    .padding(12.dp),
                verticalAlignment = Alignment.CenterVertically,
            ) {
                Icon(
                    imageVector = Icons.Rounded.Search,
                    contentDescription = null,
                    tint = Zinc200,
                )
                Spacer(Modifier.width(12.dp))
                field()
            }
        }
    )
}

@Stable
fun sweepGradient(
    colors: List<Color>,
    center: Offset = Offset.Unspecified,
    rotation: Float = 0f,
): Brush {
    require(colors.size >= 2) { "At least 2 colors are required" }
    val rotationFraction = (-rotation / 360f) % 1f
    val normalizedRotation = if (rotationFraction < 0) rotationFraction + 1f else rotationFraction
    val step = 1f / colors.size
    val rotatedStops = mutableListOf<Pair<Float, Color>>()

    for (i in colors.indices) {
        val originalStop = i * step
        val rotatedStop = (originalStop + normalizedRotation) % 1f
        rotatedStops.add(rotatedStop to colors[i])
    }

    rotatedStops.sortBy { it.first }
    val firstStop = rotatedStops.first().first
    val lastStop = rotatedStops.last().first

    if (firstStop > 0.0001f) {
        val colorBeforeWrap = rotatedStops.last().second
        val colorAfterWrap = rotatedStops.first().second
        val gapSize = (1f - lastStop) + firstStop
        val fractionAt0 = (1f - lastStop) / gapSize
        val interpolatedColor = lerp(colorBeforeWrap, colorAfterWrap, fractionAt0)
        rotatedStops.add(0, 0f to interpolatedColor)
        rotatedStops.add(1f to interpolatedColor)
    } else {
        val lastColor = rotatedStops.last().second
        rotatedStops.add(1f to lastColor)
    }

    return Brush.sweepGradient(
        colorStops = rotatedStops.toTypedArray(),
        center = center,
    )
}
Mastodon