Skip to content

Instantly share code, notes, and snippets.

@Rohit-554
Last active December 6, 2025 00:01
Show Gist options
  • Select an option

  • Save Rohit-554/9e755b1133a82b4173a37bd7f2a48031 to your computer and use it in GitHub Desktop.

Select an option

Save Rohit-554/9e755b1133a82b4173a37bd7f2a48031 to your computer and use it in GitHub Desktop.
This composable displays a horizontally scrollable week-style date selector with a 3D rotating dial effect. As the user scrolls, each date card scales, rotates, and fades based on its distance from the center, making the middle item appear highlighted. Tapping a date selects it and automatically scrolls The UI uses simple Material text styles, s…
// add this to your commonMain.dependencies
// implementation("org.jetbrains.kotlinx:kotlinx-datetime:0.7.1")
@OptIn(ExperimentalTime::class)
@Composable
fun CarouselCalendar() {
//// Clock.System.now() -> Output: 2025-11-15T14:30:45.123456789Z
//// (Year-Month-Day T Hour:Minute:Second.Nanoseconds Z for UTC)
//// Output: 2025-11-15T20:00:45.123456789 (if you're in IST, UTC+5:30)
val today = Clock.System.now().toLocalDateTime(TimeZone.currentSystemDefault()).date
// Output: 2025-11-15
val year = today.year // 2025
// Always start from 1st January
val startDate = LocalDate(year, 1, 1)
val endDate = LocalDate(year, 12, 31)
// Generate full year date list
val dates = remember {
generateSequence(startDate) { date ->
val next = date.plus(1, DateTimeUnit.DAY)
if (next <= endDate) next else null
}.toList()
}
// Selected item is today's date (even though list starts at Jan 1)
var selectedDate by remember { mutableStateOf(today) }
BoxWithConstraints(
modifier = Modifier.fillMaxWidth()
) {
DialerWeekCalendar(
dates = dates,
selectedDate = selectedDate,
onDateSelected = { selectedDate = it },
maxWidth = maxWidth
)
}
}
@Composable
fun DialerWeekCalendar(
modifier: Modifier = Modifier,
dates: List<LocalDate>,
selectedDate: LocalDate,
onDateSelected: (LocalDate) -> Unit,
maxWidth: Dp
) {
val listState = rememberLazyListState()
var isInitialLoad by remember { mutableStateOf(true) }
// Auto-scroll to center the selected date
LaunchedEffect(selectedDate) {
val selectedIndex = dates.indexOf(selectedDate)
if (selectedIndex != -1) {
if (isInitialLoad) {
listState.scrollToItem(selectedIndex)
isInitialLoad = false
} else {
listState.animateScrollToItem(selectedIndex)
}
}
}
LazyRow(
modifier = modifier
.fillMaxWidth()
.height(120.dp),
state = listState,
horizontalArrangement = Arrangement.spacedBy(8.dp),
verticalAlignment = Alignment.CenterVertically,
contentPadding = PaddingValues(horizontal = (maxWidth / 2) - 46.dp)
) {
items(dates.size) { index ->
val date = dates[index]
// Center calculation
val layoutInfo = listState.layoutInfo
val viewportCenter =
layoutInfo.viewportStartOffset +
(layoutInfo.viewportEndOffset - layoutInfo.viewportStartOffset) / 2
val itemInfo = layoutInfo.visibleItemsInfo.find { it.index == index }
val itemCenter = itemInfo?.let { it.offset + it.size / 2 } ?: 0
// Distance from center
val distanceFromCenter = if (itemInfo != null) {
abs(viewportCenter - itemCenter).toFloat() / layoutInfo.viewportSize.width
} else 1f
val scale = (1f - (distanceFromCenter * 0.3f)).coerceIn(0.7f, 1f)
val rotationY = (distanceFromCenter * 40f).coerceAtMost(45f)
val alpha = (1f - (distanceFromCenter * 0.5f)).coerceIn(0.5f, 1f)
Box(
modifier = Modifier
.graphicsLayer {
scaleX = scale
scaleY = scale
this.rotationY =
if (itemCenter < viewportCenter) rotationY else -rotationY
this.alpha = alpha
}
.clip(RoundedCornerShape(12.dp))
.background(
if (date == selectedDate) Color(0xFF1E88E5) else Color.White
)
.clickable { onDateSelected(date) }
.width(80.dp)
.height(100.dp),
contentAlignment = Alignment.Center
) {
Column(
horizontalAlignment = Alignment.CenterHorizontally,
verticalArrangement = Arrangement.Center
) {
Text(
text = date.month.name.take(3),
style = MaterialTheme.typography.bodySmall.copy(
color = if (date == selectedDate) Color.White.copy(alpha = 0.8f)
else Color.DarkGray
)
)
Spacer(modifier = Modifier.height(2.dp))
Text(
text = date.dayOfMonth.toString(),
style = MaterialTheme.typography.headlineSmall.copy(
fontWeight = FontWeight.Bold,
color = if (date == selectedDate) Color.White else Color.Black
)
)
Spacer(modifier = Modifier.height(2.dp))
Text(
text = date.dayOfWeek.name.take(3),
style = MaterialTheme.typography.bodySmall.copy(
color = if (date == selectedDate) Color.White.copy(alpha = 0.8f)
else Color.Gray
)
)
}
}
}
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment