|
1 | | -import { AnimatePresence, motion } from 'motion/react' |
2 | | -import { useEffect, useRef, useState } from 'react' |
| 1 | +import { motion } from 'motion/react' |
| 2 | +import { useEffect, useState } from 'react' |
| 3 | +import { Swiper, SwiperSlide } from 'swiper/react' |
3 | 4 | import { getFromStorage, setToStorage } from '../../common/storage' |
4 | | -import { useGetWeatherByLatLon } from '../../services/getMethodHooks/weather/getWeatherByLatLon' |
5 | | -import type { FetchedWeather } from '../../services/getMethodHooks/weather/weather.interface' |
6 | | - |
7 | | -import { Colors } from '../../common/constant/colors.constant' |
8 | 5 | import { useWeatherStore } from '../../context/weather.context' |
9 | 6 | import { useGetForecastWeatherByLatLon } from '../../services/getMethodHooks/weather/getForecastWeatherByLatLon' |
| 7 | +import { useGetWeatherByLatLon } from '../../services/getMethodHooks/weather/getWeatherByLatLon' |
| 8 | +import type { FetchedWeather } from '../../services/getMethodHooks/weather/weather.interface' |
10 | 9 | import { CurrentWeatherBox } from './components/current-box.component' |
11 | 10 | import { ForecastComponent } from './components/forecast.component' |
| 11 | +//@ts-ignore |
| 12 | +import 'swiper/css' |
| 13 | +//@ts-ignore |
| 14 | +import 'swiper/css/pagination' |
| 15 | +//@ts-ignore |
| 16 | +import 'swiper/css/navigation' |
| 17 | +import { FiChevronLeft, FiChevronRight } from 'react-icons/fi' |
| 18 | +import { FreeMode, Navigation, Pagination } from 'swiper/modules' |
12 | 19 |
|
13 | 20 | export function WeatherLayout() { |
14 | 21 | const { selectedCity, weatherSettings } = useWeatherStore() |
15 | 22 | const [cityWeather, setCityWeather] = useState<FetchedWeather | null>(null) |
16 | 23 | const [forecast, setForecast] = useState<FetchedWeather['forecast'] | null>([]) |
17 | | - const [isExpanded, setIsExpanded] = useState(false) |
18 | | - const contentRef = useRef<HTMLDivElement>(null) |
19 | | - |
20 | | - // Track content height for animation |
21 | | - const [contentHeight, setContentHeight] = useState(210) |
| 24 | + if (selectedCity === null) return null |
22 | 25 |
|
23 | 26 | const { data, dataUpdatedAt } = useGetWeatherByLatLon( |
24 | 27 | selectedCity.lat, |
@@ -60,108 +63,86 @@ export function WeatherLayout() { |
60 | 63 | } |
61 | 64 | }, [dataUpdatedAt]) |
62 | 65 |
|
63 | | - // Measure content height when content or expanded state changes |
64 | | - useEffect(() => { |
65 | | - if (contentRef.current && isExpanded) { |
66 | | - setContentHeight(contentRef.current.scrollHeight) |
67 | | - } |
68 | | - }, [forecast, isExpanded]) |
69 | | - |
70 | | - const visibleItems = isExpanded ? forecast : forecast?.slice(0, 4) |
71 | | - const hasMoreItems = forecast && forecast.length > 4 |
72 | | - |
73 | | - // Toggle with smooth animation |
74 | | - const toggleExpand = () => { |
75 | | - if (!isExpanded && contentRef.current) { |
76 | | - // Before expanding, measure content height |
77 | | - setContentHeight(contentRef.current.scrollHeight) |
78 | | - } |
79 | | - setIsExpanded((prev) => !prev) |
80 | | - } |
81 | | - |
82 | 66 | return ( |
83 | 67 | <> |
84 | 68 | <section className="rounded"> |
85 | | - <div className="flex flex-col gap-1"> |
| 69 | + <div className="flex flex-col gap-2"> |
86 | 70 | {cityWeather ? <CurrentWeatherBox weather={cityWeather.weather} /> : null} |
87 | 71 |
|
88 | 72 | <motion.div |
89 | | - ref={contentRef} |
90 | | - className="relative overflow-hidden rounded-lg " |
91 | | - animate={{ |
92 | | - height: isExpanded ? contentHeight : 210, |
93 | | - opacity: 1, |
94 | | - }} |
| 73 | + className="relative p-1 mt-2 overflow-hidden" |
95 | 74 | initial={{ opacity: 0.9 }} |
96 | | - transition={{ |
97 | | - height: { |
98 | | - duration: 0.5, |
99 | | - ease: [0.04, 0.62, 0.23, 0.98], // Custom smooth easing |
100 | | - }, |
101 | | - opacity: { duration: 0.3 }, |
102 | | - }} |
| 75 | + animate={{ opacity: 1 }} |
| 76 | + transition={{ duration: 0.3 }} |
103 | 77 | > |
104 | | - <div className="grid grid-cols-2 grid-rows-2 gap-2 p-1 overflow-visible"> |
105 | | - <AnimatePresence> |
106 | | - {visibleItems?.map((item, index) => ( |
| 78 | + <Swiper |
| 79 | + modules={[Pagination, Navigation, FreeMode]} |
| 80 | + spaceBetween={8} |
| 81 | + slidesPerView="auto" |
| 82 | + freeMode={true} |
| 83 | + pagination={false} |
| 84 | + navigation={{ |
| 85 | + nextEl: '.swiper-button-next-custom', |
| 86 | + prevEl: '.swiper-button-prev-custom', |
| 87 | + }} |
| 88 | + className="py-2 weather-forecast-slider" |
| 89 | + dir="ltr" |
| 90 | + > |
| 91 | + {forecast?.map((item, index) => ( |
| 92 | + <SwiperSlide key={`${item.date}-${index}`} className="w-auto"> |
107 | 93 | <motion.div |
108 | | - key={`${item.date}-${index}`} |
109 | | - initial={index >= 4 ? { opacity: 0, y: 10 } : { opacity: 1, y: 0 }} |
| 94 | + initial={{ opacity: 0, y: 10 }} |
110 | 95 | animate={{ opacity: 1, y: 0 }} |
111 | | - exit={index >= 4 ? { opacity: 0, y: -10 } : {}} |
112 | 96 | transition={{ |
113 | 97 | duration: 0.3, |
114 | | - delay: index >= 4 ? index * 0.05 : 0, |
| 98 | + delay: index * 0.05, |
115 | 99 | }} |
116 | 100 | > |
117 | 101 | <ForecastComponent |
118 | 102 | forecast={item} |
119 | 103 | unit={weatherSettings.temperatureUnit} |
120 | 104 | /> |
121 | 105 | </motion.div> |
122 | | - ))} |
123 | | - </AnimatePresence> |
124 | | - </div> |
| 106 | + </SwiperSlide> |
| 107 | + ))} |
125 | 108 |
|
126 | | - {hasMoreItems && ( |
127 | | - <div |
128 | | - className={`absolute bottom-0 left-0 right-0 flex items-center justify-center ${Colors.bgDiv}`} |
129 | | - > |
130 | | - <motion.button |
131 | | - onClick={toggleExpand} |
132 | | - className="flex items-center justify-center w-full py-1 transition-colors backdrop-blur-sm hover:bg-gray-700/80" |
133 | | - whileTap={{ scale: 0.95 }} |
134 | | - > |
135 | | - <motion.div |
136 | | - animate={{ rotate: isExpanded ? 180 : 0 }} |
137 | | - transition={{ |
138 | | - duration: 0.5, |
139 | | - delay: isExpanded ? 0 : 0.1, |
140 | | - ease: 'anticipate', |
141 | | - }} |
142 | | - > |
143 | | - <svg |
144 | | - xmlns="http://www.w3.org/2000/svg" |
145 | | - className="w-5 h-5 text-blue-300" |
146 | | - viewBox="0 0 20 20" |
147 | | - fill="currentColor" |
148 | | - > |
149 | | - <path |
150 | | - fillRule="evenodd" |
151 | | - d="M5.293 7.293a1 1 0 011.414 0L10 10.586l3.293-3.293a1 1 0 111.414 1.414l-4 4a1 1 0 01-1.414 0l-4-4a1 1 0 010-1.414z" |
152 | | - clipRule="evenodd" |
153 | | - /> |
154 | | - </svg> |
155 | | - </motion.div> |
156 | | - <span className="sr-only"> |
157 | | - {isExpanded ? 'نمایش کمتر' : 'نمایش بیشتر'} |
158 | | - </span> |
159 | | - </motion.button> |
| 109 | + <div className="absolute left-0 z-10 flex items-center justify-center w-8 h-8 text-white transition-all -translate-y-1/2 rounded-full shadow-lg cursor-pointer swiper-button-prev-custom top-1/2 bg-gray-800/80 backdrop-blur-lg hover:bg-gray-700/90"> |
| 110 | + <FiChevronLeft size={20} /> |
160 | 111 | </div> |
161 | | - )} |
| 112 | + |
| 113 | + <div className="absolute right-0 z-10 flex items-center justify-center w-8 h-8 text-white transition-all -translate-y-1/2 rounded-full shadow-lg cursor-pointer swiper-button-next-custom top-1/2 bg-gray-800/80 backdrop-blur-lg hover:bg-gray-700/90"> |
| 114 | + <FiChevronRight size={20} /> |
| 115 | + </div> |
| 116 | + </Swiper> |
162 | 117 | </motion.div> |
163 | 118 | </div> |
164 | 119 | </section> |
| 120 | + |
| 121 | + <style>{` |
| 122 | + .weather-forecast-slider { |
| 123 | + padding-right: 12px !important; |
| 124 | + padding-left: 12px !important; |
| 125 | + padding-bottom: 5px !important; |
| 126 | + } |
| 127 | + .weather-forecast-slider .swiper-slide { |
| 128 | + width: auto; |
| 129 | + height: auto; |
| 130 | + } |
| 131 | + .swiper-button-prev-custom, .swiper-button-next-custom { |
| 132 | + opacity: 0; |
| 133 | + transform: translateY(-50%) scale(0.8); |
| 134 | + transition: all 0.2s ease; |
| 135 | + } |
| 136 | + .weather-forecast-slider:hover .swiper-button-prev-custom, |
| 137 | + .weather-forecast-slider:hover .swiper-button-next-custom { |
| 138 | + opacity: 1; |
| 139 | + transform: translateY(-50%) scale(1); |
| 140 | + } |
| 141 | + .swiper-button-disabled { |
| 142 | + opacity: 0.3 !important; |
| 143 | + cursor: not-allowed; |
| 144 | + } |
| 145 | +`}</style> |
165 | 146 | </> |
166 | 147 | ) |
167 | 148 | } |
0 commit comments