redesign: bento home + semantic tokens + solid cards
All checks were successful
Deploy / deploy (push) Successful in 2m43s
All checks were successful
Deploy / deploy (push) Successful in 2m43s
- introduces semantic CSS tokens (--surface-1/2/3, --border-subtle/strong,
--hairline, --shadow-sm/md/lg/xl) with distinct dark and light values;
fixes broken light theme caused by hardcoded rgba(255,255,255,X)
- drops glassmorphism on cards — solid var(--surface-1) with 1px border
and layered shadows; glass kept only for aurora page background
- introduces .card/.card-raised/.card-hero utility classes
- Home page restructured into a bento grid:
* greeting row with inline day/date
* hero weather (64px number, large icon, ощущается/влажность/ветер)
next to the tram widget (1fr 1.1fr)
* forecast as a single hairline-separated band (no per-day cards)
* events+notes in a 2-column grid; events card combines today and
tomorrow with a divider; notes card styled via surface tokens
- TransportWidget repainted to use tokens, larger numbers (32px for the
next arrival), imminent highlight uses color-mix against surface-2
This commit is contained in:
359
app/page.tsx
359
app/page.tsx
@@ -419,94 +419,199 @@ function HomeTab({ weather, sensors }: { weather: WeatherData | null; sensors: S
|
||||
|
||||
|
||||
return (
|
||||
<div style={{ flex: 1, overflowY: 'auto', WebkitOverflowScrolling: 'touch' as any, padding: '20px 24px 28px', display: 'flex', flexDirection: 'column', gap: 14 }}>
|
||||
{/* Greeting + hint */}
|
||||
<div>
|
||||
<h1 style={{ fontSize: 26, fontWeight: 800, color: 'var(--text-primary)', letterSpacing: '-0.5px', margin: 0 }}>
|
||||
{greeting} 👋
|
||||
<div style={{
|
||||
flex: 1, overflowY: 'auto', WebkitOverflowScrolling: 'touch' as any,
|
||||
padding: '18px 22px 24px',
|
||||
display: 'flex', flexDirection: 'column', gap: 14,
|
||||
}}>
|
||||
{/* ───── Greeting row ───── */}
|
||||
<div style={{ display: 'flex', alignItems: 'center', justifyContent: 'space-between', gap: 16 }}>
|
||||
<h1 style={{
|
||||
fontSize: 28, fontWeight: 800, color: 'var(--text-primary)',
|
||||
letterSpacing: '-0.6px', margin: 0, lineHeight: 1.1,
|
||||
}}>
|
||||
{greeting} <span style={{ fontSize: 26 }}>👋</span>
|
||||
</h1>
|
||||
<div style={{
|
||||
fontSize: 12, color: 'var(--text-secondary)', fontWeight: 600,
|
||||
textTransform: 'capitalize', textAlign: 'right',
|
||||
letterSpacing: '-0.1px',
|
||||
}}>
|
||||
{new Date().toLocaleDateString('ru-RU', { weekday: 'long', day: 'numeric', month: 'long' })}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Weather — full width compact */}
|
||||
{weather && (
|
||||
<div style={{
|
||||
background: 'linear-gradient(135deg, rgba(99,102,241,0.1), rgba(139,92,246,0.05))',
|
||||
backdropFilter: 'blur(20px)', border: '1px solid rgba(129,140,248,0.1)',
|
||||
borderRadius: 20, padding: '18px 22px',
|
||||
display: 'flex', alignItems: 'center', gap: 20,
|
||||
position: 'relative', overflow: 'hidden',
|
||||
{/* ───── Bento row: Hero weather + Tram ───── */}
|
||||
<div style={{ display: 'grid', gridTemplateColumns: '1fr 1.1fr', gap: 14, minHeight: 230 }}>
|
||||
|
||||
{/* Hero weather card */}
|
||||
{weather ? (
|
||||
<div
|
||||
className="card-hero"
|
||||
onClick={() => weather?.forecast?.[0] && setSelectedDay(weather.forecast[0])}
|
||||
style={{
|
||||
padding: '22px 24px',
|
||||
display: 'flex', flexDirection: 'column',
|
||||
position: 'relative', overflow: 'hidden',
|
||||
cursor: 'pointer',
|
||||
}}
|
||||
>
|
||||
{/* Decorative animation, large, behind */}
|
||||
<div style={{
|
||||
position: 'absolute', top: -24, right: -12,
|
||||
opacity: 0.14, pointerEvents: 'none',
|
||||
}}>
|
||||
<WeatherAnimation condition={weather.desc} size={160} />
|
||||
</div>
|
||||
|
||||
<div style={{
|
||||
fontSize: 11, color: 'var(--text-tertiary)', fontWeight: 700,
|
||||
textTransform: 'uppercase', letterSpacing: '0.12em', marginBottom: 8,
|
||||
position: 'relative', zIndex: 1,
|
||||
}}>
|
||||
Сейчас
|
||||
</div>
|
||||
|
||||
<div style={{
|
||||
display: 'flex', alignItems: 'center', gap: 12,
|
||||
position: 'relative', zIndex: 1, marginBottom: 6,
|
||||
}}>
|
||||
<div style={{
|
||||
fontSize: 64, fontWeight: 800, letterSpacing: '-3px',
|
||||
color: 'var(--text-primary)', lineHeight: 0.9,
|
||||
fontVariantNumeric: 'tabular-nums',
|
||||
}}>
|
||||
{weather.temp}°
|
||||
</div>
|
||||
<WeatherAnimation condition={weather.desc} size={52} />
|
||||
</div>
|
||||
|
||||
<div style={{
|
||||
fontSize: 16, color: 'var(--text-primary)', fontWeight: 600,
|
||||
position: 'relative', zIndex: 1, marginBottom: 12,
|
||||
}}>
|
||||
{weather.desc}
|
||||
</div>
|
||||
|
||||
<div style={{
|
||||
display: 'flex', gap: 16, flexWrap: 'wrap',
|
||||
marginTop: 'auto', position: 'relative', zIndex: 1,
|
||||
}}>
|
||||
{weather.feelsLike && (
|
||||
<div>
|
||||
<div style={{ fontSize: 10, color: 'var(--text-tertiary)', fontWeight: 700, textTransform: 'uppercase', letterSpacing: '0.08em' }}>Ощущается</div>
|
||||
<div style={{ fontSize: 15, color: 'var(--text-primary)', fontWeight: 700, marginTop: 2 }}>{weather.feelsLike}°</div>
|
||||
</div>
|
||||
)}
|
||||
{weather.humidity && (
|
||||
<div>
|
||||
<div style={{ fontSize: 10, color: 'var(--text-tertiary)', fontWeight: 700, textTransform: 'uppercase', letterSpacing: '0.08em' }}>Влажность</div>
|
||||
<div style={{ fontSize: 15, color: 'var(--text-primary)', fontWeight: 700, marginTop: 2 }}>{weather.humidity}</div>
|
||||
</div>
|
||||
)}
|
||||
{weather.windSpeed && (
|
||||
<div>
|
||||
<div style={{ fontSize: 10, color: 'var(--text-tertiary)', fontWeight: 700, textTransform: 'uppercase', letterSpacing: '0.08em' }}>Ветер</div>
|
||||
<div style={{ fontSize: 15, color: 'var(--text-primary)', fontWeight: 700, marginTop: 2 }}>{weather.windSpeed}</div>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
) : (
|
||||
<div className="card-hero" style={{ padding: '22px 24px', display: 'flex', alignItems: 'center', justifyContent: 'center', color: 'var(--text-tertiary)', fontSize: 13 }}>
|
||||
Загрузка погоды...
|
||||
</div>
|
||||
)}
|
||||
|
||||
{/* Tram */}
|
||||
<TransportWidget />
|
||||
</div>
|
||||
|
||||
{/* ───── Forecast band (no cards, hairline-separated) ───── */}
|
||||
{weather?.forecast && (
|
||||
<div className="card" style={{
|
||||
padding: '12px 6px',
|
||||
display: 'flex', alignItems: 'stretch',
|
||||
}}>
|
||||
<div style={{ position: 'absolute', top: -15, right: 5, opacity: 0.1, pointerEvents: 'none' }}>
|
||||
<WeatherAnimation condition={weather.desc} size={90} />
|
||||
</div>
|
||||
|
||||
{/* Current */}
|
||||
<div onClick={() => weather?.forecast?.[0] && setSelectedDay(weather.forecast[0])} style={{ display: 'flex', alignItems: 'center', gap: 14, flexShrink: 0, position: 'relative', zIndex: 1, cursor: 'pointer' }}>
|
||||
<WeatherAnimation condition={weather.desc} size={48} />
|
||||
<div>
|
||||
<div style={{ fontSize: 32, fontWeight: 800, color: 'var(--text-primary)', lineHeight: 1, letterSpacing: '-2px' }}>{weather.temp}°</div>
|
||||
<div style={{ fontSize: 13, color: 'var(--text-secondary)', marginTop: 3, fontWeight: 500 }}>{weather.desc}</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Divider */}
|
||||
<div style={{ width: 1, height: 50, background: 'rgba(255,255,255,0.08)', flexShrink: 0 }} />
|
||||
|
||||
{/* 7 day forecast */}
|
||||
{weather.forecast && (
|
||||
<div style={{ display: 'flex', gap: 4, flex: 1, overflow: 'hidden' }}>
|
||||
{weather.forecast.map((day, idx) => {
|
||||
const d = new Date(day.date)
|
||||
const isToday = idx === 0
|
||||
return (
|
||||
<div key={day.date} onClick={() => setSelectedDay(day)} style={{
|
||||
flex: 1, minWidth: 0, textAlign: 'center', padding: '4px 2px',
|
||||
borderRadius: 10, cursor: 'pointer',
|
||||
background: isToday ? 'rgba(99,102,241,0.1)' : 'transparent',
|
||||
{weather.forecast.map((day, idx) => {
|
||||
const d = new Date(day.date)
|
||||
const isToday = idx === 0
|
||||
return (
|
||||
<div key={day.date} style={{
|
||||
flex: 1, display: 'flex', alignItems: 'stretch',
|
||||
borderRight: idx < weather.forecast!.length - 1 ? '1px solid var(--hairline)' : 'none',
|
||||
}}>
|
||||
<button
|
||||
onClick={() => setSelectedDay(day)}
|
||||
style={{
|
||||
flex: 1, padding: '8px 4px', borderRadius: 14,
|
||||
background: isToday ? 'var(--surface-2)' : 'transparent',
|
||||
display: 'flex', flexDirection: 'column', alignItems: 'center', gap: 4,
|
||||
transition: 'background 0.2s ease',
|
||||
}}
|
||||
>
|
||||
<div style={{
|
||||
fontSize: 10, fontWeight: 700, letterSpacing: '0.08em', textTransform: 'uppercase',
|
||||
color: isToday ? 'var(--accent)' : 'var(--text-tertiary)',
|
||||
}}>
|
||||
<div style={{ fontSize: 9, color: isToday ? '#a5b4fc' : 'var(--text-secondary)', fontWeight: 600, marginBottom: 2, overflow: 'hidden', textOverflow: 'ellipsis' }}>
|
||||
{isToday ? 'Сей' : d.toLocaleDateString('ru-RU', { weekday: 'short' }).slice(0, 2)}
|
||||
</div>
|
||||
<div style={{ fontSize: 14, marginBottom: 2 }}>{getWeatherIcon(day.desc)}</div>
|
||||
<div style={{ fontSize: 11, fontWeight: 700, color: 'var(--text-primary)' }}>{day.maxTemp}°</div>
|
||||
<div style={{ fontSize: 9, color: 'var(--text-secondary)' }}>{day.minTemp}°</div>
|
||||
{isToday ? 'Сей' : d.toLocaleDateString('ru-RU', { weekday: 'short' }).replace('.', '').slice(0, 2)}
|
||||
</div>
|
||||
)
|
||||
})}
|
||||
</div>
|
||||
)}
|
||||
<div style={{ fontSize: 20 }}>{getWeatherIcon(day.desc)}</div>
|
||||
<div style={{
|
||||
fontSize: 14, fontWeight: 800, color: 'var(--text-primary)',
|
||||
letterSpacing: '-0.5px', fontVariantNumeric: 'tabular-nums',
|
||||
}}>
|
||||
{day.maxTemp}°
|
||||
</div>
|
||||
<div style={{
|
||||
fontSize: 11, color: 'var(--text-tertiary)', fontWeight: 500,
|
||||
fontVariantNumeric: 'tabular-nums',
|
||||
}}>
|
||||
{day.minTemp}°
|
||||
</div>
|
||||
</button>
|
||||
</div>
|
||||
)
|
||||
})}
|
||||
</div>
|
||||
)}
|
||||
|
||||
{/* Transport: tram arrivals at Ул. Антонова-Овсеенко, both directions */}
|
||||
<TransportWidget />
|
||||
|
||||
{/* Two columns: Events + Notes */}
|
||||
{/* ───── Events + Notes row ───── */}
|
||||
<div style={{ display: 'grid', gridTemplateColumns: '1fr 1fr', gap: 14, flex: 1, minHeight: 0 }}>
|
||||
|
||||
{/* Left: Today + Tomorrow events */}
|
||||
<div style={{ display: 'flex', flexDirection: 'column', gap: 14 }}>
|
||||
{/* Events — today + tomorrow in one card */}
|
||||
<div className="card" style={{ padding: '18px 20px', display: 'flex', flexDirection: 'column', gap: 14, overflowY: 'auto' }}>
|
||||
{/* Today */}
|
||||
<div style={{ background: 'rgba(255,255,255,0.03)', border: '1px solid rgba(255,255,255,0.06)', borderRadius: 20, padding: '18px 20px' }}>
|
||||
<div style={{ display: 'flex', alignItems: 'center', gap: 8, marginBottom: 12 }}>
|
||||
<Calendar size={14} color="var(--text-secondary)" />
|
||||
<span style={{ fontSize: 11, color: 'var(--text-secondary)', textTransform: 'uppercase', letterSpacing: '0.08em', fontWeight: 600 }}>Сегодня</span>
|
||||
<span style={{ fontSize: 11, color: 'var(--text-tertiary)' }}>{todayEvents.length}</span>
|
||||
<div>
|
||||
<div style={{ display: 'flex', alignItems: 'center', gap: 8, marginBottom: 10 }}>
|
||||
<Calendar size={13} color="var(--text-secondary)" />
|
||||
<span style={{ fontSize: 10, color: 'var(--text-secondary)', textTransform: 'uppercase', letterSpacing: '0.1em', fontWeight: 700 }}>Сегодня</span>
|
||||
<span style={{
|
||||
fontSize: 10, color: 'var(--text-tertiary)',
|
||||
background: 'var(--surface-2)', padding: '2px 7px', borderRadius: 8,
|
||||
fontWeight: 700, fontVariantNumeric: 'tabular-nums',
|
||||
}}>
|
||||
{todayEvents.length}
|
||||
</span>
|
||||
</div>
|
||||
{calLoading ? (
|
||||
<div style={{ fontSize: 13, color: 'var(--text-secondary)' }}>Загрузка...</div>
|
||||
<div style={{ fontSize: 13, color: 'var(--text-tertiary)' }}>Загрузка...</div>
|
||||
) : todayEvents.length === 0 ? (
|
||||
<div style={{ fontSize: 14, color: 'var(--text-secondary)', textAlign: 'center', padding: '8px 0' }}>Свободный день</div>
|
||||
<div style={{ fontSize: 14, color: 'var(--text-tertiary)', textAlign: 'center', padding: '6px 0' }}>Свободный день ✨</div>
|
||||
) : (
|
||||
<div style={{ display: 'flex', flexDirection: 'column', gap: 6 }}>
|
||||
{todayEvents.map(ev => (
|
||||
<div key={ev.id} style={{ display: 'flex', alignItems: 'center', gap: 10, padding: '10px 12px', borderRadius: 12, background: `${ev.color}08`, border: `1px solid ${ev.color}12` }}>
|
||||
<div style={{ width: 3, borderRadius: 2, background: ev.color, alignSelf: 'stretch', minHeight: 30, flexShrink: 0 }} />
|
||||
<div key={ev.id} style={{
|
||||
display: 'flex', alignItems: 'center', gap: 10,
|
||||
padding: '8px 10px', borderRadius: 12,
|
||||
background: 'var(--surface-2)',
|
||||
border: '1px solid var(--border-subtle)',
|
||||
}}>
|
||||
<div style={{ width: 3, borderRadius: 2, background: ev.color, alignSelf: 'stretch', minHeight: 28, flexShrink: 0 }} />
|
||||
<div style={{ flex: 1, minWidth: 0 }}>
|
||||
<div style={{ fontSize: 13, fontWeight: 600, color: 'var(--text-primary)', overflow: 'hidden', textOverflow: 'ellipsis', whiteSpace: 'nowrap' }}>{ev.title}</div>
|
||||
<div style={{ fontSize: 11, color: 'var(--text-secondary)', marginTop: 2 }}>
|
||||
{ev.allDay ? 'Весь день' : formatEventTime(ev.start)} · <span style={{ color: ev.color }}>{ev.ownerName}</span>
|
||||
{ev.allDay ? 'Весь день' : formatEventTime(ev.start)} · <span style={{ color: ev.color, fontWeight: 600 }}>{ev.ownerName}</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@@ -515,23 +620,34 @@ function HomeTab({ weather, sensors }: { weather: WeatherData | null; sensors: S
|
||||
)}
|
||||
</div>
|
||||
|
||||
{/* Divider */}
|
||||
{(tomorrowEvents.length > 0 || todayEvents.length > 0) && (
|
||||
<div style={{ height: 1, background: 'var(--hairline)' }} />
|
||||
)}
|
||||
|
||||
{/* Tomorrow */}
|
||||
<div style={{ background: 'rgba(255,255,255,0.02)', border: '1px solid rgba(255,255,255,0.04)', borderRadius: 20, padding: '18px 20px' }}>
|
||||
<div style={{ display: 'flex', alignItems: 'center', gap: 8, marginBottom: 12 }}>
|
||||
<Calendar size={14} color="var(--text-secondary)" />
|
||||
<span style={{ fontSize: 11, color: 'var(--text-secondary)', textTransform: 'uppercase', letterSpacing: '0.08em', fontWeight: 600 }}>Завтра</span>
|
||||
<span style={{ fontSize: 11, color: 'var(--text-tertiary)' }}>{tomorrowEvents.length}</span>
|
||||
<div>
|
||||
<div style={{ display: 'flex', alignItems: 'center', gap: 8, marginBottom: 10 }}>
|
||||
<Calendar size={13} color="var(--text-tertiary)" />
|
||||
<span style={{ fontSize: 10, color: 'var(--text-tertiary)', textTransform: 'uppercase', letterSpacing: '0.1em', fontWeight: 700 }}>Завтра</span>
|
||||
<span style={{
|
||||
fontSize: 10, color: 'var(--text-tertiary)',
|
||||
background: 'var(--surface-2)', padding: '2px 7px', borderRadius: 8,
|
||||
fontWeight: 700, fontVariantNumeric: 'tabular-nums',
|
||||
}}>
|
||||
{tomorrowEvents.length}
|
||||
</span>
|
||||
</div>
|
||||
{tomorrowEvents.length === 0 ? (
|
||||
<div style={{ fontSize: 14, color: 'var(--text-secondary)', textAlign: 'center', padding: '8px 0' }}>Нет событий</div>
|
||||
<div style={{ fontSize: 13, color: 'var(--text-tertiary)', textAlign: 'center', padding: '4px 0' }}>—</div>
|
||||
) : (
|
||||
<div style={{ display: 'flex', flexDirection: 'column', gap: 6 }}>
|
||||
<div style={{ display: 'flex', flexDirection: 'column', gap: 5 }}>
|
||||
{tomorrowEvents.map(ev => (
|
||||
<div key={ev.id} style={{ display: 'flex', alignItems: 'center', gap: 10, padding: '10px 12px', borderRadius: 12, background: `${ev.color}06` }}>
|
||||
<div style={{ width: 3, borderRadius: 2, background: ev.color, alignSelf: 'stretch', minHeight: 24, flexShrink: 0, opacity: 0.6 }} />
|
||||
<div key={ev.id} style={{ display: 'flex', alignItems: 'center', gap: 10, padding: '6px 10px' }}>
|
||||
<div style={{ width: 3, borderRadius: 2, background: ev.color, alignSelf: 'stretch', minHeight: 22, flexShrink: 0, opacity: 0.7 }} />
|
||||
<div style={{ flex: 1, minWidth: 0 }}>
|
||||
<div style={{ fontSize: 13, fontWeight: 500, color: 'var(--text-primary)', overflow: 'hidden', textOverflow: 'ellipsis', whiteSpace: 'nowrap' }}>{ev.title}</div>
|
||||
<div style={{ fontSize: 11, color: 'var(--text-secondary)', marginTop: 2 }}>
|
||||
<div style={{ fontSize: 11, color: 'var(--text-tertiary)', marginTop: 1 }}>
|
||||
{ev.allDay ? 'Весь день' : formatEventTime(ev.start)}
|
||||
</div>
|
||||
</div>
|
||||
@@ -542,53 +658,62 @@ function HomeTab({ weather, sensors }: { weather: WeatherData | null; sensors: S
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Right: Pinned notes / shopping lists */}
|
||||
<div style={{ display: 'flex', flexDirection: 'column', gap: 14 }}>
|
||||
{/* Notes */}
|
||||
<div className="card" style={{ padding: '18px 20px', display: 'flex', flexDirection: 'column', gap: 12, overflowY: 'auto' }}>
|
||||
<div style={{ display: 'flex', alignItems: 'center', gap: 8 }}>
|
||||
<StickyNote size={13} color="var(--text-secondary)" />
|
||||
<span style={{ fontSize: 10, color: 'var(--text-secondary)', textTransform: 'uppercase', letterSpacing: '0.1em', fontWeight: 700 }}>Заметки</span>
|
||||
</div>
|
||||
|
||||
{pinnedNotes.length === 0 ? (
|
||||
<div style={{ background: 'rgba(255,255,255,0.02)', border: '1px solid rgba(255,255,255,0.04)', borderRadius: 20, padding: '18px 20px', flex: 1, display: 'flex', alignItems: 'center', justifyContent: 'center' }}>
|
||||
<div style={{ textAlign: 'center', color: 'var(--text-secondary)' }}>
|
||||
<StickyNote size={24} style={{ margin: '0 auto 8px', opacity: 0.3 }} />
|
||||
<div style={{ fontSize: 13 }}>Заметки появятся здесь</div>
|
||||
<div style={{ flex: 1, display: 'flex', alignItems: 'center', justifyContent: 'center', textAlign: 'center', color: 'var(--text-tertiary)' }}>
|
||||
<div>
|
||||
<StickyNote size={22} style={{ margin: '0 auto 6px', opacity: 0.4 }} />
|
||||
<div style={{ fontSize: 12 }}>Заметки появятся здесь</div>
|
||||
</div>
|
||||
</div>
|
||||
) : (
|
||||
pinnedNotes.map(note => {
|
||||
const doneCount = note.items?.filter((i: any) => i.done).length || 0
|
||||
const totalCount = note.items?.length || 0
|
||||
return (
|
||||
<div key={note.id} style={{
|
||||
background: `${note.color}08`, border: `1px solid ${note.color}15`,
|
||||
borderRadius: 20, padding: '18px 20px',
|
||||
}}>
|
||||
<div style={{ display: 'flex', alignItems: 'center', gap: 8, marginBottom: 10 }}>
|
||||
{note.type === 'shopping' ? <ShoppingCart size={14} color={note.color} /> : <FileText size={14} color={note.color} />}
|
||||
<span style={{ fontSize: 13, fontWeight: 600, color: 'var(--text-primary)' }}>{note.title}</span>
|
||||
{note.type === 'shopping' && totalCount > 0 && (
|
||||
<span style={{ fontSize: 11, color: note.color, marginLeft: 'auto' }}>{doneCount}/{totalCount}</span>
|
||||
)}
|
||||
</div>
|
||||
{note.type === 'shopping' ? (
|
||||
<div style={{ display: 'flex', flexDirection: 'column', gap: 4 }}>
|
||||
{(note.items || []).filter((i: any) => !i.done).slice(0, 5).map((item: any) => (
|
||||
<div key={item.id} style={{ fontSize: 13, color: 'var(--text-primary)', display: 'flex', alignItems: 'center', gap: 6 }}>
|
||||
<div style={{ width: 6, height: 6, borderRadius: 2, border: `1.5px solid ${note.color}50`, flexShrink: 0 }} />
|
||||
{item.text}
|
||||
</div>
|
||||
))}
|
||||
{(note.items || []).filter((i: any) => !i.done).length > 5 && (
|
||||
<div style={{ fontSize: 11, color: 'var(--text-secondary)' }}>
|
||||
+{(note.items || []).filter((i: any) => !i.done).length - 5} ещё
|
||||
</div>
|
||||
<div style={{ display: 'flex', flexDirection: 'column', gap: 8 }}>
|
||||
{pinnedNotes.map(note => {
|
||||
const doneCount = note.items?.filter((i: any) => i.done).length || 0
|
||||
const totalCount = note.items?.length || 0
|
||||
return (
|
||||
<div key={note.id} style={{
|
||||
padding: '10px 12px', borderRadius: 12,
|
||||
background: 'var(--surface-2)',
|
||||
border: '1px solid var(--border-subtle)',
|
||||
borderLeft: `3px solid ${note.color}`,
|
||||
}}>
|
||||
<div style={{ display: 'flex', alignItems: 'center', gap: 8, marginBottom: 6 }}>
|
||||
{note.type === 'shopping' ? <ShoppingCart size={12} color={note.color} /> : <FileText size={12} color={note.color} />}
|
||||
<span style={{ fontSize: 13, fontWeight: 700, color: 'var(--text-primary)', flex: 1, minWidth: 0, overflow: 'hidden', textOverflow: 'ellipsis', whiteSpace: 'nowrap' }}>{note.title}</span>
|
||||
{note.type === 'shopping' && totalCount > 0 && (
|
||||
<span style={{ fontSize: 11, color: note.color, fontWeight: 700, fontVariantNumeric: 'tabular-nums' }}>{doneCount}/{totalCount}</span>
|
||||
)}
|
||||
</div>
|
||||
) : (
|
||||
<div style={{ fontSize: 13, color: 'var(--text-secondary)', lineHeight: 1.5, overflow: 'hidden', display: '-webkit-box', WebkitLineClamp: 4, WebkitBoxOrient: 'vertical' as any }}>
|
||||
{note.text || 'Пустая заметка'}
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
)
|
||||
})
|
||||
{note.type === 'shopping' ? (
|
||||
<div style={{ display: 'flex', flexDirection: 'column', gap: 3 }}>
|
||||
{(note.items || []).filter((i: any) => !i.done).slice(0, 4).map((item: any) => (
|
||||
<div key={item.id} style={{ fontSize: 12, color: 'var(--text-secondary)', display: 'flex', alignItems: 'center', gap: 6 }}>
|
||||
<div style={{ width: 5, height: 5, borderRadius: 2, background: note.color, opacity: 0.6, flexShrink: 0 }} />
|
||||
<span style={{ overflow: 'hidden', textOverflow: 'ellipsis', whiteSpace: 'nowrap' }}>{item.text}</span>
|
||||
</div>
|
||||
))}
|
||||
{(note.items || []).filter((i: any) => !i.done).length > 4 && (
|
||||
<div style={{ fontSize: 10, color: 'var(--text-tertiary)', marginLeft: 11 }}>
|
||||
+{(note.items || []).filter((i: any) => !i.done).length - 4} ещё
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
) : (
|
||||
<div style={{ fontSize: 12, color: 'var(--text-secondary)', lineHeight: 1.5, overflow: 'hidden', display: '-webkit-box', WebkitLineClamp: 3, WebkitBoxOrient: 'vertical' as any }}>
|
||||
{note.text || 'Пустая заметка'}
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
)
|
||||
})}
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
Reference in New Issue
Block a user