Product Details
Use these Compose Multiplatform Product Details components to educate your users about all details of your products.Simply copy and paste them into your projects
Product details
data class ProductColor(val label: String, val color: Long) data class ProductSizes(val label: String, val available: Boolean = true) val photos = listOf( "https://images.unsplash.com/photo-1545066230-919660a9290a?q=80&w=1024&auto=format&fit=crop&ixlib=rb-4.0.3&ixid=M3wxMjA3fDB8MHxwaG90by1wYWdlfHx8fGVufDB8fHx8fA%3D%3D", "https://images.unsplash.com/photo-1464278860589-b2ed64f87e22?q=80&w=1024&auto=format&fit=crop&ixlib=rb-4.0.3&ixid=M3wxMjA3fDB8MHxwaG90by1wYWdlfHx8fGVufDB8fHx8fA%3D%3D", "https://images.unsplash.com/photo-1455095692583-a6db7b07d3f3?q=80&w=1024&auto=format&fit=crop&ixlib=rb-4.0.3&ixid=M3wxMjA3fDB8MHxwaG90by1wYWdlfHx8fGVufDB8fHx8fA%3D%3D", "https://images.unsplash.com/photo-1509316557442-08a701c1ecf1?q=80&w=1024&auto=format&fit=crop&ixlib=rb-4.0.3&ixid=M3wxMjA3fDB8MHxwaG90by1wYWdlfHx8fGVufDB8fHx8fA%3D%3D", "https://images.unsplash.com/photo-1495772507711-3afc975ff36e?q=80&w=1024&auto=format&fit=crop&ixlib=rb-4.0.3&ixid=M3wxMjA3fDB8MHxwaG90by1wYWdlfHx8fGVufDB8fHx8fA%3D%3D", ) val productColors = listOf( ProductColor(label = "Brown", color = 0xFF795548), ProductColor(label = "Deep Red", color = 0xFFD32F2F), ProductColor(label = "White", color = 0xFFFFFF), ProductColor(label = "Black", color = 0xFF000000), ) val productSizes = listOf( ProductSizes(label = "XXS", available = false), ProductSizes(label = "XS"), ProductSizes(label = "S"), ProductSizes(label = "M"), ProductSizes(label = "L"), ProductSizes(label = "XL"), ProductSizes(label = "XXL", available = false), ProductSizes(label = "XXXL", available = false), ) BoxWithConstraints(modifier = Modifier.fillMaxSize(), contentAlignment = Alignment.TopCenter) { @Composable fun HeroSection(animateCornerRadius: Boolean, modifier: Modifier = Modifier) { Box(modifier) { val pagerState = rememberPagerState(initialPage = 0, pageCount = { photos.size }) val previousEnabled = pagerState.currentPage > 0 val nextEnabled = pagerState.currentPage < photos.lastIndex val scope = rememberCoroutineScope() val cornerRadius by animateDpAsState(targetValue = if (pagerState.isScrollInProgress || animateCornerRadius.not()) 8.dp else 0.dp) HorizontalPager( modifier = Modifier.matchParentSize(), state = pagerState, pageSpacing = 8.dp, beyondBoundsPageCount = 2, ) { i -> AsyncImage( model = photos[i], contentDescription = null, modifier = Modifier.clip(RoundedCornerShape(cornerRadius)).fillMaxSize(), contentScale = ContentScale.Crop ) } AnimatedVisibility(visible = previousEnabled, enter = slideInHorizontally { -it } + fadeIn(), exit = slideOutHorizontally { -it * 2 } + fadeOut(), modifier = Modifier.align(Alignment.CenterStart).padding(start = 16.dp)) { Box( modifier = Modifier.clip(CircleShape).background(Color.White).clickable( role = Role.Button, onClickLabel = "See previous photo" ) { scope.launch { pagerState.animateScrollToPage(pagerState.currentPage - 1) } } ) { Icon( Lucide.ChevronLeft, contentDescription = null, tint = Color(0xFF212121), modifier = Modifier.padding(8.dp) ) } } AnimatedVisibility( visible = nextEnabled, enter = slideInHorizontally { it } + fadeIn(), exit = slideOutHorizontally { it * 2 } + fadeOut(), modifier = Modifier.align(Alignment.CenterEnd).padding(end = 16.dp) ) { Box(Modifier.clip(CircleShape).background(Color.White).clickable( role = Role.Button, onClickLabel = "See next photo" ) { scope.launch { pagerState.animateScrollToPage(pagerState.currentPage + 1) } }) { Icon( Lucide.ChevronRight, contentDescription = null, tint = Color(0xFF212121), modifier = Modifier.padding(8.dp) ) } } } } val useHorizontalLayout = maxWidth > 600.dp Row(Modifier.widthIn(max = 1200.dp).fillMaxWidth()) { if (useHorizontalLayout) { HeroSection(animateCornerRadius = false, Modifier.padding(16.dp).height(800.dp).fillMaxWidth(1 / 2f)) } LazyColumn(Modifier.fillMaxHeight()) { if (useHorizontalLayout.not()) { item { HeroSection(animateCornerRadius = true, Modifier.aspectRatio(4 / 5f).fillMaxWidth()) } } item { Spacer(Modifier.height(16.dp).fillMaxWidth()) } item { Column(Modifier.padding(horizontal = 16.dp)) { BasicText("Off-road boots", style = ComposeTheme.textStyles.base) Spacer(Modifier.height(4.dp)) BasicText("$340", style = ComposeTheme.textStyles.base.copy(fontWeight = FontWeight.Bold)) } } item { Row( modifier = Modifier .padding(horizontal = 16.dp) .offset(x = -4.dp).clip(RoundedCornerShape(4.dp)) .clickable { /* TODO */ } .padding(vertical = 4.dp), verticalAlignment = Alignment.Bottom) { Row(horizontalArrangement = Arrangement.spacedBy(-2.dp)) { repeat(4) { Icon(Icons.Rounded.Star, contentDescription = null, tint = Color(0xFFFFC107)) } Icon(Icons.Rounded.Star, contentDescription = null, tint = Color(0xFFBDBDBD)) } Spacer(Modifier.width(4.dp)) BasicText( text = "4.8 (3.4)", style = ComposeTheme.textStyles.base.copy( color = Color(0xFF3F51B5), fontWeight = FontWeight.Medium ), ) } } item { Spacer(Modifier.height(16.dp).fillMaxWidth()) } item { Column(Modifier.padding(horizontal = 16.dp)) { BasicText("Color", style = ComposeTheme.textStyles.base) Spacer(Modifier.height(4.dp)) Row(horizontalArrangement = Arrangement.spacedBy(4.dp)) { productColors.forEach { availableColor -> Box( modifier = Modifier.size(32.dp).clip(CircleShape).clickable( role = Role.Button, onClickLabel = "Select ${availableColor.label} color" ) { /* TODO */ }.border(1.dp, Color(0xFFBDBDBD), CircleShape).padding(1.dp) .background(Color(availableColor.color), CircleShape) ) } } } } item { Spacer(Modifier.height(16.dp).fillMaxWidth()) } item { Column(Modifier.padding(horizontal = 16.dp)) { Row(horizontalArrangement = Arrangement.SpaceBetween, modifier = Modifier.fillMaxWidth()) { BasicText( modifier = Modifier.alignByBaseline(), text = "Size", style = ComposeTheme.textStyles.base.copy(fontWeight = FontWeight.Medium) ) BasicText( modifier = Modifier.alignByBaseline().offset(x = 4.dp).clip(RoundedCornerShape(4.dp)) .clickable { /* TODO */ }.padding(4.dp), text = "Size Chart", style = ComposeTheme.textStyles.base.copy( fontWeight = FontWeight.Medium, color = Color(0xFF3F51B5) ) ) } Spacer(Modifier.height(4.dp)) FlowRow( maxItemsInEachRow = 4, modifier = Modifier.fillMaxWidth(), horizontalArrangement = Arrangement.spacedBy(2.dp), verticalArrangement = Arrangement.spacedBy(2.dp) ) { productSizes.forEach { sizes -> Box( modifier = Modifier.border( width = 1.dp, color = if (sizes.available) Color(0xFFBDBDBD) else Color(0xFFBDBDBD), shape = RoundedCornerShape(4.dp) ).clip(RoundedCornerShape(4.dp)) .background(if (sizes.available) Color.White else Color(0xFFEEEEEE)).clickable( enabled = sizes.available, role = Role.Button, onClickLabel = "Select ${sizes.label} size" ) { /* TODO */ }.padding(vertical = 12.dp).weight(1f), contentAlignment = Alignment.Center ) { BasicText(sizes.label) } } } } } item { Spacer(Modifier.height(16.dp).fillMaxWidth()) } item { Box( modifier = Modifier.padding(horizontal = 16.dp).fillMaxWidth().clip(RoundedCornerShape(4.dp)) .clickable { /* TODO */ }.background(Color(0xFF3F51B5)).padding(vertical = 12.dp), contentAlignment = Alignment.Center ) { BasicText( text = "Add to Cart", style = ComposeTheme.textStyles.base.copy(color = Color.White) ) } } item { Spacer(Modifier.height(32.dp).fillMaxWidth()) } item { Column(Modifier.padding(horizontal = 16.dp)) { BasicText( "Product Details", style = ComposeTheme.textStyles.base.copy(fontWeight = FontWeight.Medium) ) Spacer(Modifier.height(8.dp)) BasicText("• Durable leather construction", style = ComposeTheme.textStyles.base) BasicText("• Lace-up fastening", style = ComposeTheme.textStyles.base) BasicText("• Padded collar for comfort", style = ComposeTheme.textStyles.base) BasicText("• Rugged sole for traction", style = ComposeTheme.textStyles.base) } } item { Spacer(Modifier.height(32.dp).fillMaxWidth()) } item { Column(Modifier.padding(horizontal = 16.dp)) { BasicText( "About this item", style = ComposeTheme.textStyles.base.copy(fontWeight = FontWeight.Medium) ) Spacer(Modifier.height(8.dp)) BasicText("• Perfect for off-road adventures", style = ComposeTheme.textStyles.base) BasicText("• Sturdy and long-lasting", style = ComposeTheme.textStyles.base) BasicText("• Designed for optimal foot support", style = ComposeTheme.textStyles.base) BasicText("• Stylish and practical", style = ComposeTheme.textStyles.base) } } item { Spacer(Modifier.height(32.dp).fillMaxWidth()) } item { Column(Modifier.padding(horizontal = 16.dp)) { BasicText( "Size & Fit", style = ComposeTheme.textStyles.base.copy(fontWeight = FontWeight.Medium) ) Spacer(Modifier.height(8.dp)) BasicText("• Fits true to size", style = ComposeTheme.textStyles.base) BasicText("• Comfortable fit with padded collar", style = ComposeTheme.textStyles.base) BasicText("• Great ankle support", style = ComposeTheme.textStyles.base) BasicText("• Suitable for wide feet", style = ComposeTheme.textStyles.base) } } item { Spacer(Modifier.height(32.dp).fillMaxWidth()) } } } }
Phone
Sit tight. We are loading your preview
Product details