-
-
Save DearDhruv/722254fe80bd23d0234bec818b9a2d2f to your computer and use it in GitHub Desktop.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| import android.annotation.SuppressLint | |
| import androidx.compose.animation.core.FastOutSlowInEasing | |
| import androidx.compose.animation.core.RepeatMode | |
| 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.clickable | |
| import androidx.compose.foundation.gestures.Orientation | |
| import androidx.compose.foundation.gestures.detectTapGestures | |
| import androidx.compose.foundation.gestures.draggable | |
| import androidx.compose.foundation.gestures.rememberDraggableState | |
| import androidx.compose.foundation.interaction.MutableInteractionSource | |
| import androidx.compose.foundation.layout.Arrangement | |
| import androidx.compose.foundation.layout.Box | |
| import androidx.compose.foundation.layout.BoxWithConstraints | |
| import androidx.compose.foundation.layout.Column | |
| import androidx.compose.foundation.layout.Row | |
| import androidx.compose.foundation.layout.fillMaxHeight | |
| import androidx.compose.foundation.layout.fillMaxSize | |
| import androidx.compose.foundation.layout.fillMaxWidth | |
| import androidx.compose.foundation.layout.height | |
| import androidx.compose.foundation.layout.offset | |
| import androidx.compose.foundation.layout.padding | |
| import androidx.compose.foundation.layout.requiredSize | |
| import androidx.compose.foundation.layout.size | |
| import androidx.compose.foundation.layout.width | |
| import androidx.compose.foundation.shape.CircleShape | |
| import androidx.compose.foundation.shape.RoundedCornerShape | |
| import androidx.compose.material.icons.Icons | |
| import androidx.compose.material.icons.filled.Close | |
| import androidx.compose.material.icons.filled.Favorite | |
| import androidx.compose.material.icons.filled.Person | |
| import androidx.compose.material.icons.filled.Search | |
| import androidx.compose.material.icons.filled.Star | |
| import androidx.compose.material3.Icon | |
| import androidx.compose.material3.Text | |
| import androidx.compose.runtime.Composable | |
| 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.clip | |
| import androidx.compose.ui.draw.dropShadow | |
| import androidx.compose.ui.draw.innerShadow | |
| import androidx.compose.ui.graphics.Color | |
| import androidx.compose.ui.graphics.Shape | |
| import androidx.compose.ui.graphics.shadow.Shadow | |
| import androidx.compose.ui.graphics.vector.ImageVector | |
| import androidx.compose.ui.input.pointer.pointerInput | |
| import androidx.compose.ui.layout.onSizeChanged | |
| import androidx.compose.ui.platform.LocalDensity | |
| import androidx.compose.ui.tooling.preview.Preview | |
| import androidx.compose.ui.unit.Dp | |
| import androidx.compose.ui.unit.DpOffset | |
| import androidx.compose.ui.unit.IntOffset | |
| import androidx.compose.ui.unit.dp | |
| import kotlin.math.roundToInt | |
| /* ------------------------------------------------------- | |
| * Palette | |
| * ---------------------------------------------------- */ | |
| private val NeoBg = Color(0xFFE9EDF6) | |
| private val NeoIcon = Color(0xFF737F92) | |
| private val NeoDark = Color(0xFF2A3B55) | |
| private val NeoLight = Color.White | |
| /* ------------------------------------------------------- | |
| * Shadow helpers (composition-safe order) | |
| * | |
| * Raised: dropShadow → clip → background | |
| * Inset : clip → background → innerShadow | |
| * ---------------------------------------------------- */ | |
| private fun Modifier.neoRaised(shape: Shape) = this | |
| .dropShadow( | |
| shape, | |
| Shadow( | |
| radius = 2.dp, | |
| spread = 0.dp, | |
| offset = DpOffset(5.dp, 5.dp), | |
| color = NeoDark.copy(alpha = 0.30f) | |
| ) | |
| ) | |
| .dropShadow( | |
| shape, | |
| Shadow(radius = 4.dp, spread = 0.dp, offset = DpOffset(-5.dp, -5.dp), color = NeoLight) | |
| ) | |
| .dropShadow( | |
| shape, | |
| Shadow( | |
| radius = 3.dp, | |
| spread = 0.dp, | |
| offset = DpOffset(2.dp, 3.dp), | |
| color = NeoDark.copy(alpha = 0.10f) | |
| ) | |
| ) | |
| private fun Modifier.neoInset(shape: Shape) = this | |
| .innerShadow( | |
| shape, | |
| Shadow(radius = 5.dp, spread = 0.dp, offset = DpOffset(-5.dp, -5.dp), color = NeoLight) | |
| ) | |
| .innerShadow( | |
| shape, | |
| Shadow( | |
| radius = 2.dp, | |
| spread = 0.dp, | |
| offset = DpOffset(5.dp, 5.dp), | |
| color = NeoDark.copy(alpha = 0.40f) | |
| ) | |
| ) | |
| /* ------------------------------------------------------- | |
| * Icon (toggle: raised ↔ inset) | |
| * ---------------------------------------------------- */ | |
| @Composable | |
| fun NeoCircleIconToggle( | |
| icon: ImageVector, | |
| contentDescription: String?, | |
| modifier: Modifier = Modifier, | |
| size: Dp = 88.dp | |
| ) { | |
| var pressed by remember { mutableStateOf(false) } | |
| val shape = CircleShape | |
| Box( | |
| modifier = modifier | |
| .requiredSize(size) | |
| .then( | |
| if (!pressed) { | |
| Modifier | |
| .dropShadow( | |
| shape, | |
| Shadow( | |
| radius = 2.dp, | |
| spread = 0.dp, | |
| offset = DpOffset(5.dp, 5.dp), | |
| color = NeoDark.copy(alpha = 0.30f) | |
| ) | |
| ) | |
| .dropShadow( | |
| shape, | |
| Shadow( | |
| radius = 4.dp, | |
| spread = 0.dp, | |
| offset = DpOffset(-5.dp, -5.dp), | |
| color = NeoLight | |
| ) | |
| ) | |
| .dropShadow( | |
| shape, | |
| Shadow( | |
| radius = 3.dp, | |
| spread = 0.dp, | |
| offset = DpOffset(2.dp, 3.dp), | |
| color = NeoDark.copy(alpha = 0.10f) | |
| ) | |
| ) | |
| .clip(shape) | |
| .background(NeoBg) | |
| } else { | |
| Modifier | |
| .clip(shape) | |
| .background(NeoBg) | |
| .innerShadow( | |
| shape, Shadow( | |
| radius = 5.dp, | |
| spread = 0.dp, | |
| offset = DpOffset(-5.dp, -5.dp), | |
| color = NeoLight | |
| ) | |
| ) | |
| .innerShadow( | |
| shape, Shadow( | |
| radius = 3.dp, | |
| spread = 0.dp, | |
| offset = DpOffset(5.dp, 5.dp), | |
| color = NeoDark.copy(alpha = 0.40f) | |
| ) | |
| ) | |
| } | |
| ) | |
| .clickable( | |
| interactionSource = remember { MutableInteractionSource() }, | |
| indication = null | |
| ) { pressed = !pressed }, | |
| contentAlignment = Alignment.Center | |
| ) { | |
| Icon(icon, contentDescription, tint = NeoIcon, modifier = Modifier.size(size * 0.34f)) | |
| } | |
| } | |
| /* ------------------------------------------------------- | |
| * Switch | |
| * ---------------------------------------------------- */ | |
| @Composable | |
| fun NeoSwitch( | |
| checked: Boolean, | |
| onCheckedChange: (Boolean) -> Unit, | |
| modifier: Modifier = Modifier, | |
| width: Dp = 64.dp, | |
| height: Dp = 36.dp | |
| ) { | |
| val trackShape = RoundedCornerShape(percent = 50) | |
| val thumbSize = height - 10.dp | |
| val thumbShape = CircleShape | |
| Box( | |
| modifier = modifier | |
| .size(width, height) | |
| .clip(trackShape) | |
| .background(NeoBg) | |
| .innerShadow( | |
| trackShape, | |
| Shadow(radius = 7.dp, offset = DpOffset(-4.dp, -4.dp), color = NeoLight) | |
| ) | |
| .innerShadow( | |
| trackShape, | |
| Shadow( | |
| radius = 2.dp, | |
| offset = DpOffset(4.dp, 4.dp), | |
| color = NeoDark.copy(alpha = 0.40f) | |
| ) | |
| ) | |
| .clickable( | |
| remember { MutableInteractionSource() }, | |
| indication = null | |
| ) { onCheckedChange(!checked) }, | |
| contentAlignment = Alignment.CenterStart | |
| ) { | |
| Box( | |
| modifier = Modifier | |
| .padding(start = 5.dp, end = 5.dp) | |
| .size(thumbSize) | |
| .then(if (!checked) Modifier else Modifier.offset(x = width - thumbSize - 10.dp)) | |
| .dropShadow( | |
| thumbShape, | |
| Shadow( | |
| radius = 2.dp, | |
| offset = DpOffset(4.dp, 3.dp), | |
| color = NeoDark.copy(alpha = 0.30f) | |
| ) | |
| ) | |
| .dropShadow( | |
| thumbShape, | |
| Shadow(radius = 4.dp, offset = DpOffset(-4.dp, -3.dp), color = NeoLight) | |
| ) | |
| .dropShadow( | |
| thumbShape, | |
| Shadow( | |
| radius = 3.dp, | |
| offset = DpOffset(2.dp, 3.dp), | |
| color = NeoDark.copy(alpha = 0.10f) | |
| ) | |
| ) | |
| .clip(thumbShape) | |
| .background(NeoBg) | |
| ) | |
| } | |
| } | |
| /* ------------------------------------------------------- | |
| * Slider | |
| * ---------------------------------------------------- */ | |
| @SuppressLint("UnusedBoxWithConstraintsScope") | |
| @Composable | |
| fun NeoSlider( | |
| value: Float, | |
| onValueChange: (Float) -> Unit, | |
| modifier: Modifier = Modifier, | |
| height: Dp = 48.dp, | |
| thumbSize: Dp = 28.dp, | |
| horizontalPadding: Dp = 16.dp | |
| ) { | |
| val trackShape = RoundedCornerShape(percent = 50) | |
| val thumbShape = CircleShape | |
| val density = LocalDensity.current | |
| var widthPx by remember { mutableStateOf(0f) } | |
| val thumbPx = with(density) { thumbSize.toPx() } | |
| val padPx = with(density) { horizontalPadding.toPx() } | |
| val travelPx = (widthPx - 2 * padPx - thumbPx).coerceAtLeast(0f) | |
| val clamped = value.coerceIn(0f, 1f) | |
| val xPx = clamped * travelPx | |
| val dragState = rememberDraggableState { delta -> | |
| if (travelPx > 0f) { | |
| val newPx = (xPx + delta).coerceIn(0f, travelPx) | |
| onValueChange(newPx / travelPx) | |
| } | |
| } | |
| BoxWithConstraints( | |
| modifier = modifier | |
| .fillMaxWidth() | |
| .height(height) | |
| .onSizeChanged { widthPx = it.width.toFloat() } | |
| .clip(trackShape) | |
| .background(NeoBg) | |
| .innerShadow( | |
| trackShape, | |
| Shadow(radius = 5.dp, offset = DpOffset(-5.dp, -5.dp), color = NeoLight) | |
| ) | |
| .innerShadow( | |
| trackShape, | |
| Shadow( | |
| radius = 2.dp, | |
| offset = DpOffset(5.dp, 5.dp), | |
| color = NeoDark.copy(alpha = 0.40f) | |
| ) | |
| ) | |
| .padding(horizontal = horizontalPadding), | |
| contentAlignment = Alignment.CenterStart | |
| ) { | |
| // Inner groove | |
| Box( | |
| modifier = Modifier | |
| .fillMaxWidth() | |
| .height(10.dp) | |
| .clip(trackShape) | |
| .background(NeoBg) | |
| .innerShadow( | |
| trackShape, | |
| Shadow(radius = 3.dp, offset = DpOffset(-2.dp, -2.dp), color = NeoLight) | |
| ) | |
| .innerShadow( | |
| trackShape, | |
| Shadow( | |
| radius = 2.dp, | |
| offset = DpOffset(2.dp, 2.dp), | |
| color = NeoDark.copy(alpha = 0.30f) | |
| ) | |
| ) | |
| ) | |
| // Progress fill (slightly inset for legibility) | |
| val progressWidth = with(LocalDensity.current) { (xPx + thumbPx / 2f).toDp() } | |
| Box( | |
| modifier = Modifier | |
| .height(10.dp) | |
| .width(progressWidth.coerceAtLeast(0.dp)) | |
| .clip(trackShape) | |
| .background(NeoBg) | |
| .innerShadow( | |
| trackShape, | |
| Shadow( | |
| radius = 3.dp, | |
| offset = DpOffset(1.dp, 1.dp), | |
| color = NeoDark.copy(alpha = 0.18f) | |
| ) | |
| ) | |
| ) | |
| // Thumb (raised) | |
| Box( | |
| modifier = Modifier | |
| .offset { IntOffset(xPx.roundToInt(), 0) } | |
| .size(thumbSize) | |
| .dropShadow( | |
| thumbShape, | |
| Shadow( | |
| radius = 2.dp, | |
| offset = DpOffset(5.dp, 5.dp), | |
| color = NeoDark.copy(alpha = 0.30f) | |
| ) | |
| ) | |
| .dropShadow( | |
| thumbShape, | |
| Shadow(radius = 4.dp, offset = DpOffset(-5.dp, -5.dp), color = NeoLight) | |
| ) | |
| .dropShadow( | |
| thumbShape, | |
| Shadow( | |
| radius = 3.dp, | |
| offset = DpOffset(2.dp, 3.dp), | |
| color = NeoDark.copy(alpha = 0.10f) | |
| ) | |
| ) | |
| .clip(thumbShape) | |
| .background(NeoBg) | |
| .draggable(state = dragState, orientation = Orientation.Horizontal) | |
| .pointerInput(travelPx) { | |
| detectTapGestures { offset -> | |
| if (travelPx > 0f) { | |
| val newPx = (offset.x - padPx - thumbPx / 2f).coerceIn(0f, travelPx) | |
| onValueChange(newPx / travelPx) | |
| } | |
| } | |
| } | |
| ) | |
| } | |
| } | |
| /* ------------------------------------------------------- | |
| * Checkbox, Radio, Chip, Button | |
| * ---------------------------------------------------- */ | |
| @Composable | |
| fun NeoCheckbox( | |
| checked: Boolean, | |
| onCheckedChange: (Boolean) -> Unit, | |
| modifier: Modifier = Modifier, | |
| size: Dp = 28.dp | |
| ) { | |
| val shape = RoundedCornerShape(6.dp) | |
| Box( | |
| modifier = modifier | |
| .requiredSize(size) | |
| .then( | |
| if (!checked) { | |
| Modifier | |
| .neoRaised(shape) | |
| .clip(shape) | |
| .background(NeoBg) | |
| } else { | |
| Modifier | |
| .clip(shape) | |
| .background(NeoBg) | |
| .neoInset(shape) | |
| } | |
| ) | |
| .clickable( | |
| remember { MutableInteractionSource() }, | |
| indication = null | |
| ) { onCheckedChange(!checked) }, | |
| contentAlignment = Alignment.Center | |
| ) { | |
| if (checked) { | |
| Icon( | |
| Icons.Default.Close, | |
| contentDescription = null, | |
| tint = NeoDark.copy(alpha = 0.45f), | |
| modifier = Modifier.size(size * 0.85f) | |
| ) | |
| } | |
| } | |
| } | |
| @Composable | |
| fun NeoTextButton( | |
| text: String, | |
| onClick: () -> Unit, | |
| modifier: Modifier = Modifier, | |
| pressed: Boolean = false, | |
| paddingH: Dp = 20.dp, | |
| height: Dp = 40.dp | |
| ) { | |
| val shape = RoundedCornerShape(999.dp) | |
| Box( | |
| modifier = modifier | |
| .height(height) | |
| .then( | |
| if (!pressed) { | |
| Modifier | |
| .neoRaised(shape) | |
| .clip(shape) | |
| .background(NeoBg) | |
| } else { | |
| Modifier | |
| .clip(shape) | |
| .background(NeoBg) | |
| .neoInset(shape) | |
| } | |
| ) | |
| .clickable( | |
| remember { MutableInteractionSource() }, | |
| indication = null, | |
| onClick = onClick | |
| ) | |
| .padding(horizontal = paddingH), | |
| contentAlignment = Alignment.Center | |
| ) { | |
| Text(text, color = NeoIcon) | |
| } | |
| } | |
| @Composable | |
| fun NeoTextButtonToggle( | |
| text: String, | |
| modifier: Modifier = Modifier, | |
| height: Dp = 40.dp, | |
| paddingH: Dp = 20.dp | |
| ) { | |
| var pressed by remember { mutableStateOf(false) } | |
| NeoTextButton( | |
| text = text, | |
| onClick = { pressed = !pressed }, | |
| modifier = modifier, | |
| pressed = pressed, | |
| paddingH = paddingH, | |
| height = height | |
| ) | |
| } | |
| @Composable | |
| fun NeoToggleChip( | |
| label: String, | |
| checked: Boolean, | |
| onCheckedChange: (Boolean) -> Unit, | |
| modifier: Modifier = Modifier | |
| ) { | |
| NeoTextButton( | |
| text = label, | |
| onClick = { onCheckedChange(!checked) }, | |
| modifier = modifier, | |
| pressed = checked | |
| ) | |
| } | |
| @Composable | |
| fun NeoRadioButton( | |
| selected: Boolean, | |
| onClick: () -> Unit, | |
| modifier: Modifier = Modifier, | |
| size: Dp = 26.dp | |
| ) { | |
| val shape = CircleShape | |
| Box( | |
| modifier = modifier | |
| .requiredSize(size) | |
| .then( | |
| if (!selected) { | |
| Modifier | |
| .neoRaised(shape) | |
| .clip(shape) | |
| .background(NeoBg) | |
| } else { | |
| Modifier | |
| .clip(shape) | |
| .background(NeoBg) | |
| .neoInset(shape) | |
| } | |
| ) | |
| .clickable(remember { MutableInteractionSource() }, indication = null) { onClick() }, | |
| contentAlignment = Alignment.Center | |
| ) { | |
| if (selected) { | |
| Box( | |
| Modifier | |
| .size(size * 0.45f) | |
| .clip(shape) | |
| .background(NeoIcon.copy(alpha = 0.9f)) | |
| ) | |
| } | |
| } | |
| } | |
| /* ------------------------------------------------------- | |
| * Linear Progress (determinate & indeterminate) | |
| * ---------------------------------------------------- */ | |
| @Composable | |
| fun NeoProgressBar( | |
| progress: Float, // 0f..1f | |
| modifier: Modifier = Modifier, | |
| height: Dp = 14.dp | |
| ) { | |
| val shape = RoundedCornerShape(percent = 50) | |
| Box( | |
| modifier = modifier | |
| .height(height) | |
| .clip(shape) | |
| .background(NeoBg) | |
| .innerShadow( | |
| shape, | |
| Shadow(radius = 5.dp, offset = DpOffset(-5.dp, -5.dp), color = NeoLight) | |
| ) | |
| .innerShadow( | |
| shape, | |
| Shadow( | |
| radius = 2.dp, | |
| offset = DpOffset(5.dp, 5.dp), | |
| color = NeoDark.copy(alpha = 0.40f) | |
| ) | |
| ), | |
| contentAlignment = Alignment.CenterStart | |
| ) { | |
| Box( | |
| modifier = Modifier | |
| .fillMaxWidth(progress.coerceIn(0f, 1f)) | |
| .fillMaxHeight() | |
| .dropShadow( | |
| shape, | |
| Shadow( | |
| radius = 2.dp, | |
| offset = DpOffset(5.dp, 5.dp), | |
| color = NeoDark.copy(alpha = 0.30f) | |
| ) | |
| ) | |
| .dropShadow( | |
| shape, | |
| Shadow(radius = 4.dp, offset = DpOffset(-5.dp, -5.dp), color = NeoLight) | |
| ) | |
| .dropShadow( | |
| shape, | |
| Shadow( | |
| radius = 3.dp, | |
| offset = DpOffset(2.dp, 3.dp), | |
| color = NeoDark.copy(alpha = 0.10f) | |
| ) | |
| ) | |
| .clip(shape) | |
| .background(NeoBg) | |
| ) | |
| } | |
| } | |
| @Composable | |
| fun NeoProgressBarIndeterminate( | |
| modifier: Modifier = Modifier, | |
| height: Dp = 14.dp, | |
| minChunk: Float = 0.18f, | |
| maxChunk: Float = 0.45f, | |
| ) { | |
| val shape = RoundedCornerShape(percent = 50) | |
| Box( | |
| modifier = modifier | |
| .height(height) | |
| .clip(shape) | |
| .background(NeoBg) | |
| .innerShadow( | |
| shape, | |
| Shadow(radius = 5.dp, offset = DpOffset(-5.dp, -5.dp), color = NeoLight) | |
| ) | |
| .innerShadow( | |
| shape, | |
| Shadow( | |
| radius = 2.dp, | |
| offset = DpOffset(5.dp, 5.dp), | |
| color = NeoDark.copy(alpha = 0.40f) | |
| ) | |
| ), | |
| contentAlignment = Alignment.CenterStart | |
| ) { | |
| // animate head/span | |
| val t = rememberInfiniteTransition(label = "pb") | |
| val head by t.animateFloat( | |
| initialValue = -maxChunk, targetValue = 1f, | |
| animationSpec = infiniteRepeatable(tween(1400, easing = FastOutSlowInEasing)), | |
| label = "head" | |
| ) | |
| val span by t.animateFloat( | |
| initialValue = minChunk, targetValue = maxChunk, | |
| animationSpec = infiniteRepeatable( | |
| tween(1400, easing = FastOutSlowInEasing), | |
| RepeatMode.Reverse | |
| ), | |
| label = "span" | |
| ) | |
| val start = (head - span).coerceIn(0f, 1f) | |
| val end = (head + span).coerceIn(0f, 1f) | |
| val frac = (end - start).coerceAtLeast(0f) | |
| BoxWithConstraints(Modifier.fillMaxSize()) { | |
| val startPad = with(LocalDensity.current) { (start * constraints.maxWidth).toDp() } | |
| Box( | |
| Modifier | |
| .fillMaxHeight() | |
| .fillMaxWidth(frac) | |
| .padding(start = startPad) | |
| .clip(shape) | |
| .background(NeoBg) | |
| .innerShadow( | |
| shape, | |
| Shadow( | |
| radius = 3.dp, | |
| offset = DpOffset(1.dp, 1.dp), | |
| color = NeoDark.copy(alpha = 0.22f) | |
| ) | |
| ) | |
| ) | |
| } | |
| } | |
| } | |
| /* ------------------------------------------------------- | |
| * Card (toggle: raised ↔ inset) | |
| * ---------------------------------------------------- */ | |
| @Composable | |
| fun NeoCardToggle( | |
| modifier: Modifier = Modifier, | |
| corner: Dp = 12.dp, | |
| content: @Composable () -> Unit | |
| ) { | |
| var pressed by remember { mutableStateOf(false) } | |
| val shape = RoundedCornerShape(corner) | |
| Box( | |
| modifier = modifier | |
| .then( | |
| if (!pressed) { | |
| Modifier | |
| .neoRaised(shape) | |
| .clip(shape) | |
| .background(NeoBg) | |
| } else { | |
| Modifier | |
| .clip(shape) | |
| .background(NeoBg) | |
| .neoInset(shape) | |
| } | |
| ) | |
| .clickable(remember { MutableInteractionSource() }, indication = null) { | |
| pressed = !pressed | |
| } | |
| ) { | |
| content() | |
| } | |
| } | |
| /* ------------------------------------------------------- | |
| * Showcase (balanced layout, no section titles) | |
| * ---------------------------------------------------- */ | |
| @Preview(showBackground = true, backgroundColor = 0xFFE9EDF6) | |
| @Composable | |
| fun NeumorphicExamplePreview() { | |
| NeumorphicExample() | |
| } | |
| @Composable | |
| fun NeumorphicExample( | |
| modifier: Modifier = Modifier, | |
| icons: List<ImageVector> = listOf( | |
| Icons.Default.Person, | |
| Icons.Default.Favorite, | |
| Icons.Default.Star, | |
| Icons.Default.Search | |
| ) | |
| ) { | |
| var switchChecked by remember { mutableStateOf(false) } | |
| var sliderValue by remember { mutableStateOf(0.45f) } | |
| var checkboxChecked by remember { mutableStateOf(true) } | |
| var chipChecked by remember { mutableStateOf(false) } | |
| var radioSelected by remember { mutableStateOf(0) } | |
| Box( | |
| modifier = modifier | |
| .fillMaxSize() | |
| .background(NeoBg) | |
| ) { | |
| Column( | |
| modifier = Modifier | |
| .align(Alignment.Center) | |
| .fillMaxWidth(0.92f) | |
| .padding(horizontal = 16.dp, vertical = 10.dp), | |
| verticalArrangement = Arrangement.spacedBy(32.dp), | |
| horizontalAlignment = Alignment.CenterHorizontally | |
| ) { | |
| // Row 1 – small controls cluster | |
| Row( | |
| horizontalArrangement = Arrangement.spacedBy(14.dp), | |
| verticalAlignment = Alignment.CenterVertically | |
| ) { | |
| NeoCheckbox( | |
| checked = checkboxChecked, | |
| onCheckedChange = { checkboxChecked = it }, | |
| size = 28.dp | |
| ) | |
| NeoToggleChip( | |
| label = if (chipChecked) "On" else "Off", | |
| checked = chipChecked, | |
| onCheckedChange = { chipChecked = it }) | |
| NeoSwitch( | |
| checked = switchChecked, | |
| onCheckedChange = { switchChecked = it }, | |
| width = 72.dp, | |
| height = 40.dp | |
| ) | |
| } | |
| // Row 2 – radio group | |
| Row( | |
| horizontalArrangement = Arrangement.spacedBy(18.dp), | |
| verticalAlignment = Alignment.CenterVertically | |
| ) { | |
| listOf(0, 1, 2).forEach { idx -> | |
| NeoRadioButton( | |
| selected = radioSelected == idx, | |
| onClick = { radioSelected = idx }) | |
| } | |
| } | |
| // Row 3 – progress bars (indeterminate then determinate) | |
| NeoProgressBarIndeterminate(modifier = Modifier.fillMaxWidth()) | |
| NeoProgressBar(progress = sliderValue, modifier = Modifier.fillMaxWidth()) | |
| // Row 4 – slider | |
| NeoSlider( | |
| value = sliderValue, | |
| onValueChange = { sliderValue = it }, | |
| modifier = Modifier.fillMaxWidth(), | |
| height = 48.dp, | |
| thumbSize = 28.dp, | |
| horizontalPadding = 16.dp | |
| ) | |
| // Row 5 – button | |
| NeoTextButtonToggle(text = "Button") | |
| // Row 6 – a few icon chips | |
| Row( | |
| horizontalArrangement = Arrangement.spacedBy(12.dp), | |
| verticalAlignment = Alignment.CenterVertically | |
| ) { | |
| icons.forEach { icon -> | |
| NeoCircleIconToggle(icon = icon, contentDescription = null, size = 56.dp) | |
| } | |
| } | |
| // Row 7 – square card (raised → inset on tap) | |
| NeoCardToggle( | |
| modifier = Modifier | |
| .fillMaxWidth() | |
| .height(64.dp), | |
| corner = 0.dp | |
| ) { | |
| Row( | |
| Modifier | |
| .fillMaxSize() | |
| .padding(horizontal = 16.dp), | |
| horizontalArrangement = Arrangement.SpaceBetween, | |
| verticalAlignment = Alignment.CenterVertically | |
| ) { | |
| Text("Neumorphic card", color = NeoIcon) | |
| NeoToggleChip( | |
| label = "Chip", | |
| checked = chipChecked, | |
| onCheckedChange = { chipChecked = it }) | |
| } | |
| } | |
| } | |
| } | |
| } |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment