Files
smart-home-tablet/components/TopBar.tsx
Cosmo eb644ff341
All checks were successful
Deploy / deploy (push) Successful in 2m40s
feat: premium UI redesign — glassmorphism, gradient accents, ambient background
2026-04-22 18:38:31 +00:00

248 lines
9.3 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
'use client'
import { useState, useEffect } from 'react'
import { Droplets, Wind, Thermometer, X } from 'lucide-react'
interface WeatherData {
temp: string
desc: string
humidity: string
windSpeed: string
feelsLike: string
forecast?: { date: string; maxTemp: string; minTemp: string; desc: string }[]
}
interface SensorData {
temperature: number
humidity: number
pm25: number
}
interface TopBarProps {
weather: WeatherData | null
sensors: SensorData | null
}
function getWeatherIcon(desc: string): string {
const d = desc?.toLowerCase() || ''
if (d.includes('ясно') || d.includes('солнеч')) return '☀️'
if (d.includes('облач')) return '⛅'
if (d.includes('пасмурн')) return '☁️'
if (d.includes('дождь') || d.includes('морос')) return '🌧️'
if (d.includes('снег')) return '❄️'
if (d.includes('гроз')) return '⛈️'
return '🌤️'
}
function formatTime(date: Date): string {
return date.toLocaleTimeString('ru-RU', { hour: '2-digit', minute: '2-digit' })
}
function formatDate(date: Date): string {
const weekday = date.toLocaleDateString('ru-RU', { weekday: 'long' })
const day = date.getDate()
const month = date.toLocaleDateString('ru-RU', { month: 'long' })
return `${weekday}, ${day} ${month}`
}
export default function TopBar({ weather, sensors }: TopBarProps) {
const [time, setTime] = useState(() => new Date())
const [showModal, setShowModal] = useState(false)
useEffect(() => {
const t = setInterval(() => setTime(new Date()), 1000)
return () => clearInterval(t)
}, [])
return (
<>
<header
style={{
height: 72,
display: 'flex',
alignItems: 'center',
justifyContent: 'space-between',
padding: '0 24px',
borderBottom: '1px solid rgba(255,255,255,0.04)',
background: 'transparent',
flexShrink: 0,
position: 'relative',
zIndex: 5,
}}
>
{/* Left: time + date */}
<div style={{ display: 'flex', alignItems: 'baseline', gap: 12 }}>
<span style={{
fontSize: 28,
fontWeight: 700,
color: 'var(--text-primary)',
letterSpacing: '-1px',
fontVariantNumeric: 'tabular-nums',
}}>
{formatTime(time)}
</span>
<span style={{
fontSize: 14,
color: 'var(--text-secondary)',
fontWeight: 400,
textTransform: 'capitalize',
}}>
{formatDate(time)}
</span>
</div>
{/* Right: sensors + weather */}
<div style={{ display: 'flex', alignItems: 'center', gap: 6 }}>
{/* Room sensors */}
{sensors && (
<div style={{
display: 'flex',
alignItems: 'center',
gap: 4,
padding: '8px 14px',
borderRadius: 14,
background: 'rgba(255,255,255,0.03)',
border: '1px solid rgba(255,255,255,0.05)',
}}>
<Thermometer size={14} color="rgba(255,255,255,0.35)" />
<span style={{ fontSize: 13, fontWeight: 600, color: 'var(--text-primary)', marginRight: 8 }}>
{sensors.temperature}°
</span>
<Droplets size={14} color="rgba(255,255,255,0.35)" />
<span style={{ fontSize: 13, fontWeight: 600, color: 'var(--text-primary)', marginRight: 8 }}>
{sensors.humidity}%
</span>
<Wind size={14} color="rgba(255,255,255,0.35)" />
<span style={{ fontSize: 13, color: 'var(--text-secondary)' }}>
{sensors.pm25}
</span>
</div>
)}
{/* Weather widget */}
{weather && (
<button
onClick={() => setShowModal(true)}
style={{
display: 'flex',
alignItems: 'center',
gap: 8,
padding: '8px 16px',
borderRadius: 14,
background: 'linear-gradient(135deg, rgba(99,102,241,0.1), rgba(139,92,246,0.08))',
border: '1px solid rgba(129,140,248,0.15)',
color: 'var(--text-primary)',
transition: 'all 0.25s ease',
}}
>
<span style={{ fontSize: 20 }}>{getWeatherIcon(weather.desc)}</span>
<span style={{ fontSize: 15, fontWeight: 700 }}>{weather.temp}°</span>
<span style={{ fontSize: 12, color: 'var(--text-secondary)', fontWeight: 500 }}>{weather.desc}</span>
</button>
)}
</div>
</header>
{/* Weather Modal */}
{showModal && weather && (
<div
onClick={() => setShowModal(false)}
style={{
position: 'fixed',
inset: 0,
background: 'rgba(0,0,0,0.7)',
backdropFilter: 'blur(12px)',
zIndex: 100,
display: 'flex',
alignItems: 'center',
justifyContent: 'center',
}}
>
<div
onClick={e => e.stopPropagation()}
style={{
background: 'rgba(18, 18, 35, 0.95)',
backdropFilter: 'blur(40px)',
border: '1px solid rgba(255,255,255,0.08)',
borderRadius: 24,
padding: 32,
minWidth: 360,
maxWidth: 420,
boxShadow: '0 25px 60px rgba(0,0,0,0.5)',
}}
>
<div style={{ display: 'flex', justifyContent: 'space-between', alignItems: 'flex-start', marginBottom: 24 }}>
<div style={{ display: 'flex', alignItems: 'center', gap: 16 }}>
<span style={{ fontSize: 48 }}>{getWeatherIcon(weather.desc)}</span>
<div>
<div style={{ fontSize: 40, fontWeight: 800, lineHeight: 1, letterSpacing: '-2px' }}>{weather.temp}°</div>
<div style={{ fontSize: 15, color: 'var(--text-secondary)', marginTop: 4, fontWeight: 500 }}>{weather.desc}</div>
</div>
</div>
<button onClick={() => setShowModal(false)} style={{ color: 'var(--text-secondary)', padding: 4 }}>
<X size={20} />
</button>
</div>
<div style={{
display: 'grid',
gridTemplateColumns: 'repeat(3, 1fr)',
gap: 10,
marginBottom: 24,
}}>
{[
{ icon: <Droplets size={16} />, label: 'Влажность', value: `${weather.humidity}%` },
{ icon: <Wind size={16} />, label: 'Ветер', value: `${weather.windSpeed} км/ч` },
{ icon: <Thermometer size={16} />, label: 'Ощущается', value: `${weather.feelsLike}°` },
].map(item => (
<div key={item.label} style={{
padding: '14px 12px',
borderRadius: 16,
background: 'rgba(255,255,255,0.04)',
border: '1px solid rgba(255,255,255,0.06)',
textAlign: 'center',
}}>
<div style={{ color: 'var(--text-secondary)', marginBottom: 8, display: 'flex', justifyContent: 'center' }}>{item.icon}</div>
<div style={{ fontSize: 16, fontWeight: 700, color: 'var(--text-primary)' }}>{item.value}</div>
<div style={{ fontSize: 11, color: 'var(--text-secondary)', marginTop: 4 }}>{item.label}</div>
</div>
))}
</div>
{weather.forecast && weather.forecast.length > 0 && (
<>
<div style={{ fontSize: 11, color: 'var(--text-secondary)', textTransform: 'uppercase', letterSpacing: '0.1em', fontWeight: 600, marginBottom: 14 }}>
Прогноз
</div>
<div style={{ display: 'flex', flexDirection: 'column', gap: 6 }}>
{weather.forecast.map(day => {
const d = new Date(day.date)
const label = d.toLocaleDateString('ru-RU', { weekday: 'short', day: 'numeric', month: 'short' })
return (
<div key={day.date} style={{
display: 'flex',
alignItems: 'center',
justifyContent: 'space-between',
padding: '10px 14px',
borderRadius: 12,
background: 'rgba(255,255,255,0.03)',
}}>
<span style={{ fontSize: 13, color: 'var(--text-secondary)', minWidth: 100, textTransform: 'capitalize' }}>{label}</span>
<span style={{ fontSize: 18 }}>{getWeatherIcon(day.desc)}</span>
<span style={{ fontSize: 12, color: 'var(--text-secondary)', minWidth: 80, textAlign: 'center' }}>{day.desc}</span>
<span style={{ fontSize: 14, fontWeight: 600, color: 'var(--text-primary)' }}>
{day.maxTemp}° <span style={{ color: 'var(--text-secondary)', fontWeight: 400 }}>/ {day.minTemp}°</span>
</span>
</div>
)
})}
</div>
</>
)}
</div>
</div>
)}
</>
)
}