Blocks

Auth

A two-screen registration flow with a colorful mosaic splash, form fields, and social sign-up options.

Welcome to B-List

Record everything you need in one place in a few seconds and easily.

Installation

caveui components are copy-paste Jetpack Compose built entirely on Material 3 — there's no caveui dependency to add. Make sure Material 3 is on your classpath (it ships with the Compose BOM), then copy the Usage snippet below into your project.

kotlin
// build.gradle.kts (module)
dependencies {
    implementation(platform("androidx.compose:compose-bom:2025.06.00"))
    implementation("androidx.compose.material3:material3")
}

Usage

kotlin
// ── Welcome screen ─────────────────────────────────────────────
@Composable
fun WelcomeScreen(onRegister: () -> Unit, onSignIn: () -> Unit) {
    val accent = Color(0xFF7C3AED) // violet-700
    Column(Modifier.fillMaxSize().background(MaterialTheme.colorScheme.background)) {
        MosaicPattern(Modifier.fillMaxWidth().height(260.dp))
        Column(
            Modifier
                .weight(1f)
                .padding(horizontal = 28.dp)
                .padding(bottom = 36.dp),
            verticalArrangement = Arrangement.Center,
        ) {
            Text(
                "Welcome to B-List",
                style = MaterialTheme.typography.headlineMedium,
                fontWeight = FontWeight.ExtraBold,
            )
            Text(
                "Record everything you need in one place " +
                    "in a few seconds and easily.",
                modifier = Modifier.padding(top = 12.dp),
                style = MaterialTheme.typography.bodyMedium,
                color = MaterialTheme.colorScheme.onSurfaceVariant,
                textAlign = TextAlign.Center,
            )
            Spacer(Modifier.height(28.dp))
            Row(horizontalArrangement = Arrangement.spacedBy(12.dp)) {
                Button(
                    onClick = onRegister,
                    modifier = Modifier.weight(1f),
                    shape = CircleShape,
                    colors = ButtonDefaults.buttonColors(
                        containerColor = accent,
                    ),
                ) { Text("Register") }
                OutlinedButton(
                    onClick = onSignIn,
                    modifier = Modifier.weight(1f),
                    shape = CircleShape,
                    border = BorderStroke(1.5.dp, accent),
                    colors = ButtonDefaults.outlinedButtonColors(
                        contentColor = accent,
                    ),
                ) { Text("Sign in") }
            }
        }
    }
}

// ── Register screen ──────────────────────────────────────────────
@Composable
fun RegisterScreen(onBack: () -> Unit, onRegister: () -> Unit) {
    val accent = Color(0xFF7C3AED)
    var name by remember { mutableStateOf("") }
    var email by remember { mutableStateOf("") }
    var password by remember { mutableStateOf("") }
    var passwordVisible by remember { mutableStateOf(false) }

    Column(Modifier.fillMaxSize().background(MaterialTheme.colorScheme.background)) {
        // Mosaic header with back button
        Box {
            MosaicPattern(
                Modifier
                    .fillMaxWidth()
                    .height(140.dp)
                    .align(Alignment.TopEnd),
            )
            IconButton(
                onClick = onBack,
                modifier = Modifier.padding(top = 8.dp, start = 4.dp),
            ) {
                Icon(
                    Icons.AutoMirrored.Filled.ArrowBack,
                    contentDescription = "Back",
                )
            }
        }

        Column(
            Modifier
                .weight(1f)
                .verticalScroll(rememberScrollState())
                .padding(horizontal = 24.dp)
                .padding(bottom = 32.dp),
        ) {
            Text(
                "Create a new account",
                style = MaterialTheme.typography.headlineSmall,
                fontWeight = FontWeight.ExtraBold,
            )
            Text(
                "Please enter your information below to create an account.",
                modifier = Modifier.padding(top = 8.dp),
                style = MaterialTheme.typography.bodySmall,
                color = MaterialTheme.colorScheme.onSurfaceVariant,
            )
            Spacer(Modifier.height(20.dp))

            OutlinedTextField(
                value = name,
                onValueChange = { name = it },
                placeholder = { Text("Full name") },
                singleLine = true,
                shape = RoundedCornerShape(14.dp),
                modifier = Modifier.fillMaxWidth(),
            )
            Spacer(Modifier.height(12.dp))
            OutlinedTextField(
                value = email,
                onValueChange = { email = it },
                placeholder = { Text("Email") },
                singleLine = true,
                keyboardOptions = KeyboardOptions(
                    keyboardType = KeyboardType.Email,
                ),
                shape = RoundedCornerShape(14.dp),
                modifier = Modifier.fillMaxWidth(),
            )
            Spacer(Modifier.height(12.dp))
            OutlinedTextField(
                value = password,
                onValueChange = { password = it },
                placeholder = { Text("Password") },
                singleLine = true,
                visualTransformation = if (passwordVisible)
                    VisualTransformation.None
                else
                    PasswordVisualTransformation(),
                trailingIcon = {
                    IconButton(
                        onClick = { passwordVisible = !passwordVisible },
                    ) {
                        Icon(
                            if (passwordVisible) Icons.Filled.Visibility
                            else Icons.Filled.VisibilityOff,
                            contentDescription = "Toggle password",
                        )
                    }
                },
                shape = RoundedCornerShape(14.dp),
                modifier = Modifier.fillMaxWidth(),
            )
            Spacer(Modifier.height(20.dp))

            Button(
                onClick = onRegister,
                modifier = Modifier.fillMaxWidth(),
                shape = CircleShape,
                colors = ButtonDefaults.buttonColors(
                    containerColor = accent,
                ),
            ) { Text("Register now") }

            Spacer(Modifier.height(16.dp))
            Text(
                "or sign up with",
                modifier = Modifier.fillMaxWidth(),
                textAlign = TextAlign.Center,
                style = MaterialTheme.typography.labelMedium,
                color = MaterialTheme.colorScheme.onSurfaceVariant,
            )
            Spacer(Modifier.height(12.dp))

            Row(
                Modifier.fillMaxWidth(),
                horizontalArrangement = Arrangement.Center,
            ) {
                listOf("Facebook", "Google", "Apple").forEach { provider ->
                    OutlinedIconButton(
                        onClick = {},
                        modifier = Modifier.size(48.dp),
                        shape = CircleShape,
                    ) {
                        Icon(
                            when (provider) {
                                "Facebook" -> Icons.Filled.Facebook
                                "Google" -> Icons.Filled.Android
                                else -> Icons.Filled.Apple
                            },
                            contentDescription = provider,
                            modifier = Modifier.size(20.dp),
                        )
                    }
                    Spacer(Modifier.width(12.dp))
                }
            }

            Spacer(Modifier.height(16.dp))
            Row(
                Modifier.fillMaxWidth(),
                horizontalArrangement = Arrangement.Center,
            ) {
                Text(
                    "Already have an account? ",
                    style = MaterialTheme.typography.bodySmall,
                    color = MaterialTheme.colorScheme.onSurfaceVariant,
                )
                Text(
                    "Sign In",
                    style = MaterialTheme.typography.bodySmall,
                    fontWeight = FontWeight.SemiBold,
                    color = accent,
                )
            }
        }
    }
}

// ── Mosaic composable (shared) ───────────────────────────────────
@Composable
private fun MosaicPattern(modifier: Modifier = Modifier) {
    val colors = listOf(
        Color(0xFFF97316), Color(0xFFA855F7),
        Color(0xFF14B8A6), Color(0xFFEC4899),
    )
    // 5 × 4 grid of quarter-circle tiles, 52dp each
    val tileSize = 52.dp
    val cols = 5
    val rows = 4
    Canvas(modifier.size(width = tileSize * cols, height = tileSize * rows)) {
        val px = tileSize.toPx()
        repeat(rows) { row ->
            repeat(cols) { col ->
                val idx = row * cols + col
                val color = colors[idx % colors.size]
                val rot = (idx % 4) * 90f
                withTransform({
                    translate(col * px, row * px)
                    rotate(rot, pivot = Offset(px / 2, px / 2))
                }) {
                    // A full 2×2 circle, offset so only the top-left quadrant shows
                    drawCircle(
                        color = color,
                        radius = px,
                        center = Offset(0f, 0f),
                    )
                }
            }
        }
    }
}