design(calendar): месячная сетка 1:1 по живой теме
_calendar-month-view-mobile.css: месяц — белая карточка r12 с рамкой и паддингом 12; дни недели — круги 40 #F5F5F5 (16/500, были пилюли); ячейки — карточки #FDFDFD r4 с тенью, высота 120 (были gridline 66), зазор 4; номер дня 14/500 справа-сверху; «сегодня» — зелёная плашка 40×24 r4 с белым числом (был круг); события — пилюли #ECF9DE с рамкой #70B62B r4, текст 10/12, до 3 в ячейке; вне месяца — серые. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
+96
-78
@@ -392,54 +392,58 @@ fun CalendarMonthGrid(
|
|||||||
) {
|
) {
|
||||||
val days = CalendarRepository.monthGridDays(month)
|
val days = CalendarRepository.monthGridDays(month)
|
||||||
val today = LocalDate.now()
|
val today = LocalDate.now()
|
||||||
|
// Живая тема (_calendar-month-view-mobile.css): месяц — белая карточка r12 с рамкой,
|
||||||
|
// внутри дни недели — круги 40 (#F5F5F5, 16/500) и ячейки-карточки #FDFDFD r4
|
||||||
|
// с тенью, зазор 4.
|
||||||
Column(
|
Column(
|
||||||
modifier = Modifier.fillMaxWidth(),
|
modifier = Modifier
|
||||||
verticalArrangement = Arrangement.spacedBy(6.dp),
|
.fillMaxWidth()
|
||||||
|
.clip(RoundedCornerShape(12.dp))
|
||||||
|
.background(Color.White)
|
||||||
|
.border(1.dp, F7Colors.Border, RoundedCornerShape(12.dp))
|
||||||
|
.padding(12.dp),
|
||||||
|
verticalArrangement = Arrangement.spacedBy(4.dp),
|
||||||
) {
|
) {
|
||||||
// Заголовок дней недели — серые пилюли (по макету «Календарь · Месяц»)
|
|
||||||
Row(
|
Row(
|
||||||
modifier = Modifier.fillMaxWidth(),
|
modifier = Modifier.fillMaxWidth(),
|
||||||
horizontalArrangement = Arrangement.spacedBy(4.dp),
|
horizontalArrangement = Arrangement.spacedBy(4.dp),
|
||||||
) {
|
) {
|
||||||
weekDayLabels.forEach { label ->
|
weekDayLabels.forEach { label ->
|
||||||
Box(
|
Box(modifier = Modifier.weight(1f), contentAlignment = Alignment.Center) {
|
||||||
modifier = Modifier
|
Box(
|
||||||
.weight(1f)
|
modifier = Modifier
|
||||||
.clip(RoundedCornerShape(100.dp))
|
.size(40.dp)
|
||||||
.background(F7Colors.Grey2)
|
.clip(CircleShape)
|
||||||
.padding(vertical = 6.dp),
|
.background(F7Colors.Grey2),
|
||||||
contentAlignment = Alignment.Center,
|
contentAlignment = Alignment.Center,
|
||||||
) {
|
) {
|
||||||
Text(
|
Text(
|
||||||
text = label,
|
text = label,
|
||||||
style = MaterialTheme.typography.labelMedium,
|
style = MaterialTheme.typography.bodyLarge,
|
||||||
fontWeight = FontWeight.Medium,
|
fontWeight = FontWeight.Medium,
|
||||||
color = F7Colors.TextSecondary,
|
color = F7Colors.TextPrimary,
|
||||||
)
|
)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
// Гридлайн-сетка без скруглений (по макету)
|
days.chunked(7).forEach { week ->
|
||||||
Column(
|
Row(
|
||||||
modifier = Modifier
|
modifier = Modifier.fillMaxWidth(),
|
||||||
.fillMaxWidth()
|
horizontalArrangement = Arrangement.spacedBy(4.dp),
|
||||||
.border(0.5.dp, F7Colors.Grey3),
|
) {
|
||||||
) {
|
week.forEach { day ->
|
||||||
days.chunked(7).forEach { week ->
|
val inMonth = day.month == month.month
|
||||||
Row(modifier = Modifier.fillMaxWidth()) {
|
val dayEvents = eventsByDay[day].orEmpty()
|
||||||
week.forEach { day ->
|
CalendarDayCell(
|
||||||
val inMonth = day.month == month.month
|
day = day.dayOfMonth,
|
||||||
val dayEvents = eventsByDay[day].orEmpty()
|
inMonth = inMonth,
|
||||||
CalendarDayCell(
|
isToday = day == today,
|
||||||
day = day.dayOfMonth,
|
selected = day == selectedDay,
|
||||||
inMonth = inMonth,
|
events = dayEvents,
|
||||||
isToday = day == today,
|
onClick = { onSelectDay(day) },
|
||||||
selected = day == selectedDay,
|
modifier = Modifier.weight(1f),
|
||||||
events = dayEvents,
|
)
|
||||||
onClick = { onSelectDay(day) },
|
|
||||||
modifier = Modifier.weight(1f),
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -456,65 +460,79 @@ private fun CalendarDayCell(
|
|||||||
onClick: () -> Unit,
|
onClick: () -> Unit,
|
||||||
modifier: Modifier = Modifier,
|
modifier: Modifier = Modifier,
|
||||||
) {
|
) {
|
||||||
val cellBg = when {
|
// Живая тема: ячейка — карточка #FDFDFD r4 с тонкой тенью, высота 120, паддинг 4;
|
||||||
isToday -> F7Colors.Green10
|
// номер дня 14/500 СПРАВА-сверху (чужой месяц — серый); «сегодня» — зелёная плашка
|
||||||
selected -> F7Colors.PrimaryLight
|
// 40×24 r4 с белым числом; выбранный день подсвечиваем #ECF9DE (наша адаптация тапа).
|
||||||
else -> F7Colors.Surface
|
|
||||||
}
|
|
||||||
Column(
|
Column(
|
||||||
modifier = modifier
|
modifier = modifier
|
||||||
.heightIn(min = 66.dp)
|
.height(120.dp)
|
||||||
.border(0.5.dp, F7Colors.Grey3)
|
.shadow(
|
||||||
.background(cellBg)
|
elevation = 1.dp,
|
||||||
|
shape = RoundedCornerShape(4.dp),
|
||||||
|
spotColor = F7Colors.Border,
|
||||||
|
)
|
||||||
|
.clip(RoundedCornerShape(4.dp))
|
||||||
|
.background(if (selected) F7Colors.PrimaryLight else Color(0xFFFDFDFD))
|
||||||
.clickable(onClick = onClick)
|
.clickable(onClick = onClick)
|
||||||
.padding(horizontal = 4.dp, vertical = 4.dp),
|
.padding(4.dp),
|
||||||
verticalArrangement = Arrangement.spacedBy(2.dp),
|
verticalArrangement = Arrangement.spacedBy(2.dp),
|
||||||
) {
|
) {
|
||||||
// Номер дня сверху-слева; сегодня — в зелёном круге
|
Row(modifier = Modifier.fillMaxWidth(), horizontalArrangement = Arrangement.End) {
|
||||||
Box(
|
if (isToday) {
|
||||||
modifier = Modifier
|
Box(
|
||||||
.size(22.dp)
|
modifier = Modifier
|
||||||
.then(if (isToday) Modifier.clip(CircleShape).background(F7Colors.Primary) else Modifier),
|
.width(40.dp)
|
||||||
contentAlignment = Alignment.Center,
|
.height(24.dp)
|
||||||
) {
|
.clip(RoundedCornerShape(4.dp))
|
||||||
Text(
|
.background(F7Colors.Primary),
|
||||||
text = day.toString(),
|
contentAlignment = Alignment.Center,
|
||||||
style = MaterialTheme.typography.bodySmall,
|
) {
|
||||||
fontWeight = if (isToday) FontWeight.SemiBold else FontWeight.Normal,
|
Text(
|
||||||
color = when {
|
text = day.toString(),
|
||||||
isToday -> F7Colors.TextOnPrimary
|
style = MaterialTheme.typography.bodyMedium,
|
||||||
!inMonth -> F7Colors.TextMuted
|
fontWeight = FontWeight.Medium,
|
||||||
else -> F7Colors.TextPrimary
|
color = Color.White,
|
||||||
},
|
)
|
||||||
)
|
}
|
||||||
}
|
} else {
|
||||||
if (inMonth) {
|
|
||||||
events.take(2).forEach { event ->
|
|
||||||
CalendarDayEventPill(event.summary.ifBlank { "Событие" })
|
|
||||||
}
|
|
||||||
if (events.size > 2) {
|
|
||||||
Text(
|
Text(
|
||||||
"+${events.size - 2}",
|
text = day.toString(),
|
||||||
style = MaterialTheme.typography.labelSmall,
|
style = MaterialTheme.typography.bodyMedium,
|
||||||
color = F7Colors.Primary,
|
|
||||||
fontWeight = FontWeight.Medium,
|
fontWeight = FontWeight.Medium,
|
||||||
|
color = if (inMonth) F7Colors.TextPrimary else F7Colors.TextSecondary,
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
events.take(3).forEach { event ->
|
||||||
|
CalendarDayEventPill(event.summary.ifBlank { "Событие" }, muted = !inMonth)
|
||||||
|
}
|
||||||
|
if (events.size > 3) {
|
||||||
|
Text(
|
||||||
|
"+${events.size - 3}",
|
||||||
|
style = MaterialTheme.typography.labelSmall,
|
||||||
|
color = F7Colors.Primary,
|
||||||
|
fontWeight = FontWeight.Medium,
|
||||||
|
)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@Composable
|
@Composable
|
||||||
private fun CalendarDayEventPill(text: String) {
|
private fun CalendarDayEventPill(text: String, muted: Boolean = false) {
|
||||||
|
// Живая тема: пилюля события — фон #ECF9DE, рамка #70B62B, r4, текст 10/12;
|
||||||
|
// вне текущего месяца — серые с белым текстом.
|
||||||
Text(
|
Text(
|
||||||
text = text,
|
text = text,
|
||||||
modifier = Modifier
|
modifier = Modifier
|
||||||
.fillMaxWidth()
|
.fillMaxWidth()
|
||||||
.clip(RoundedCornerShape(4.dp))
|
.clip(RoundedCornerShape(4.dp))
|
||||||
.background(F7Colors.PrimaryLight)
|
.background(if (muted) F7Colors.TextSecondary else F7Colors.PrimaryLight)
|
||||||
.padding(horizontal = 4.dp, vertical = 1.dp),
|
.border(1.dp, if (muted) F7Colors.TextSecondary else F7Colors.Primary, RoundedCornerShape(4.dp))
|
||||||
style = MaterialTheme.typography.labelSmall,
|
.padding(horizontal = 4.dp, vertical = 2.dp),
|
||||||
color = F7Colors.PrimaryDark,
|
fontSize = 10.sp,
|
||||||
|
lineHeight = 12.sp,
|
||||||
|
fontWeight = FontWeight.Medium,
|
||||||
|
color = if (muted) Color.White else F7Colors.TextPrimary,
|
||||||
maxLines = 1,
|
maxLines = 1,
|
||||||
overflow = TextOverflow.Ellipsis,
|
overflow = TextOverflow.Ellipsis,
|
||||||
)
|
)
|
||||||
|
|||||||
Reference in New Issue
Block a user