added components
This commit is contained in:
parent
52041ce92d
commit
b1a34bfb6b
9 changed files with 569 additions and 0 deletions
31
weather_app/app/components/airPollution/AirPollution.tsx
Normal file
31
weather_app/app/components/airPollution/AirPollution.tsx
Normal file
|
@ -0,0 +1,31 @@
|
||||||
|
"use client"
|
||||||
|
import { useGlobalContext } from '@/app/context/globalContext'
|
||||||
|
import { air_QualityIndex } from '@/app/utils/Misc';
|
||||||
|
import { Progress } from '@/components/ui/progress';
|
||||||
|
import { Skeleton } from '@/components/ui/skeleton';
|
||||||
|
import React from 'react'
|
||||||
|
import { FaThermometerEmpty } from "react-icons/fa";
|
||||||
|
|
||||||
|
const AirPollution = () => {
|
||||||
|
const {airPollution} = useGlobalContext();
|
||||||
|
if(!airPollution || !airPollution.list || !airPollution.list[0] || !airPollution.list[0].main){
|
||||||
|
return <Skeleton className='h-[12rem] w-full col-span-2 md:col-span-full'/>
|
||||||
|
}
|
||||||
|
const airQualityIndex = airPollution.list[0].main.aqi * 10;
|
||||||
|
console.log(airQualityIndex)
|
||||||
|
//filter aqi
|
||||||
|
const filterAQI = air_QualityIndex.find((item) => {
|
||||||
|
return item.rating === airQualityIndex;
|
||||||
|
})
|
||||||
|
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className='flex flex-col gap-8 dark:bg-darkGgray shadow-sm dark:shadow-none pt-6 px-4 h-[12rem] border rounded-lg col-span-full sm-2:col-span-2 md:col-span-2 xl:col-span-2'>
|
||||||
|
<h2 className='flex items-center gap-1 '><FaThermometerEmpty/> Air Pollution</h2>
|
||||||
|
<Progress value={airQualityIndex} max={100} className='air-progress'/>
|
||||||
|
<p>Air quality is {filterAQI?.desc}</p>
|
||||||
|
</div>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
export default AirPollution
|
75
weather_app/app/components/daily_forcast/DailyForecast.tsx
Normal file
75
weather_app/app/components/daily_forcast/DailyForecast.tsx
Normal file
|
@ -0,0 +1,75 @@
|
||||||
|
"use client"
|
||||||
|
import { useGlobalContext } from '@/app/context/globalContext'
|
||||||
|
import { Skeleton } from '@/components/ui/skeleton';
|
||||||
|
import { CloudIcon, CloudRainIcon, SnowflakeIcon, SunIcon, Zap } from 'lucide-react';
|
||||||
|
import React from 'react'
|
||||||
|
import {
|
||||||
|
Carousel,
|
||||||
|
CarouselContent,
|
||||||
|
CarouselItem,
|
||||||
|
} from "@/components/ui/carousel"
|
||||||
|
import moment from 'moment';
|
||||||
|
import { kelvinToCelsius } from '@/app/utils/Misc';
|
||||||
|
|
||||||
|
const DailyForcast = () => {
|
||||||
|
const {forecast, dailyForecast} = useGlobalContext();
|
||||||
|
const {weather} = forecast;
|
||||||
|
const {city, list} = dailyForecast;
|
||||||
|
if(!dailyForecast || !city || !list){
|
||||||
|
return <Skeleton className='h-[12rem] w-full' />
|
||||||
|
}
|
||||||
|
if(!forecast || !weather){
|
||||||
|
return <Skeleton className='h-[12rem] w-full' />
|
||||||
|
}
|
||||||
|
const today = new Date();
|
||||||
|
const todayString = today.toISOString().split("T")[0];
|
||||||
|
|
||||||
|
const todayForecast = list.filter((forecast: {dt_txt: string; main: {temp: number}}) => {
|
||||||
|
return forecast.dt_txt.startsWith(todayString);
|
||||||
|
})
|
||||||
|
|
||||||
|
const {main: weatherMain} = weather[0]
|
||||||
|
const getIcon = () => {
|
||||||
|
switch (weatherMain){
|
||||||
|
case "Snow":
|
||||||
|
return <SnowflakeIcon/>;
|
||||||
|
case "Rain":
|
||||||
|
return <CloudRainIcon/>;
|
||||||
|
case "Clear":
|
||||||
|
return <SunIcon/>;
|
||||||
|
case "Clouds":
|
||||||
|
return <CloudIcon/>;
|
||||||
|
case "Thunderstorm":
|
||||||
|
return <Zap/>;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className = 'flex flex-col justify-center gap-8 dark:bg-darkGgray shadow-sm dark:shadow-none pt-8 h-[12rem] border rounded-lg col-span-full sm-2:col-span-2 md:col-span-2 xl:col-span-2'>
|
||||||
|
<div className='h-full flex gap-4 overflow-hidden'>
|
||||||
|
{todayForecast.length < 1 ? (<div>Loading...</div>) :
|
||||||
|
(
|
||||||
|
<div className='w-full'>
|
||||||
|
<Carousel>
|
||||||
|
<CarouselContent>
|
||||||
|
{
|
||||||
|
todayForecast.map((forecast: {dt_txt: string; main: {temp: number}}) => {
|
||||||
|
return <CarouselItem key={forecast.dt_txt} className='flex flex-col gap-4 basis-[6rem] cursor-grab items-center'>
|
||||||
|
<p>{moment(forecast.dt_txt).format("hh:mm")}</p>
|
||||||
|
<p>{getIcon()}</p>
|
||||||
|
<p className='mt-4'>{kelvinToCelsius(forecast.main.temp)}°F </p>
|
||||||
|
</CarouselItem>
|
||||||
|
})
|
||||||
|
}
|
||||||
|
</CarouselContent>
|
||||||
|
</Carousel>
|
||||||
|
</div>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
export default DailyForcast
|
34
weather_app/app/components/sunset/Sunset.tsx
Normal file
34
weather_app/app/components/sunset/Sunset.tsx
Normal file
|
@ -0,0 +1,34 @@
|
||||||
|
"use client"
|
||||||
|
import { useGlobalContext } from '@/app/context/globalContext'
|
||||||
|
import { unixToTime } from '@/app/utils/Misc';
|
||||||
|
import { Skeleton } from '@/components/ui/skeleton';
|
||||||
|
import React from 'react'
|
||||||
|
import { GiSunrise } from "react-icons/gi";
|
||||||
|
import { GiSunset } from "react-icons/gi";
|
||||||
|
|
||||||
|
const Sunset = () => {
|
||||||
|
const {forecast} = useGlobalContext();
|
||||||
|
if(!forecast || !forecast.sys || !forecast?.sys?.sunset){
|
||||||
|
return <Skeleton className='h-[12rem] w-full'/>
|
||||||
|
}
|
||||||
|
const times = forecast?.sys.sunset;
|
||||||
|
const timezone = forecast?.timezone;
|
||||||
|
const timeSunset = unixToTime(times, timezone)
|
||||||
|
const timeSunrise = unixToTime(forecast?.sys?.sunrise, timezone)
|
||||||
|
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className='flex flex-col gap-5 justify-center dark:bg-darkGgray shadow-sm dark:shadow-none sm-2:col-span-2 px-5 h-[12rem] border rounded-lg'>
|
||||||
|
<div className=''>
|
||||||
|
<h2 className='flex items-center gap-1 '><GiSunrise/>Sunrise</h2>
|
||||||
|
<p className='pt-2'>{timeSunrise} am</p>
|
||||||
|
</div>
|
||||||
|
<div className=''>
|
||||||
|
<h2 className='flex items-center gap-1 '><GiSunset/> Sunset</h2>
|
||||||
|
<p className='pt-2'>{timeSunset} pm</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
export default Sunset
|
79
weather_app/app/components/temp/Temperature.tsx
Normal file
79
weather_app/app/components/temp/Temperature.tsx
Normal file
|
@ -0,0 +1,79 @@
|
||||||
|
"use client"
|
||||||
|
import { useGlobalContext } from '@/app/context/globalContext'
|
||||||
|
import { kelvinToCelsius } from '@/app/utils/Misc';
|
||||||
|
import { CloudIcon, CloudRainIcon, SnowflakeIcon, SunIcon, Zap } from 'lucide-react';
|
||||||
|
import { IoNavigateOutline } from "react-icons/io5";
|
||||||
|
|
||||||
|
import React, { useEffect, useState } from 'react'
|
||||||
|
import moment from 'moment';
|
||||||
|
|
||||||
|
const Temperature = () => {
|
||||||
|
const {forecast} = useGlobalContext();
|
||||||
|
const {main, timezone, name, weather} = forecast;
|
||||||
|
if(!forecast || !weather){
|
||||||
|
return <div>Loading...</div>
|
||||||
|
}
|
||||||
|
const temp = kelvinToCelsius(main?.temp);
|
||||||
|
const minTemp = kelvinToCelsius(main?.temp_min);
|
||||||
|
const maxTemp = kelvinToCelsius(main?.temp_max);
|
||||||
|
|
||||||
|
//set time, day, weather
|
||||||
|
const [localTime, setLocalTime] = useState<String>("");
|
||||||
|
const [currentDay, setCurrentDay] = useState<String>("");
|
||||||
|
const {main: weatherMain, description} = weather[0];
|
||||||
|
const getIcon = () => {
|
||||||
|
switch (weatherMain){
|
||||||
|
case "Snow":
|
||||||
|
return <SnowflakeIcon/>;
|
||||||
|
case "Rain":
|
||||||
|
return <CloudRainIcon/>;
|
||||||
|
case "Clear":
|
||||||
|
return <SunIcon/>;
|
||||||
|
case "Clouds":
|
||||||
|
return <CloudIcon/>;
|
||||||
|
case "Thunderstorm":
|
||||||
|
return <Zap/>;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
//real time update
|
||||||
|
useEffect(() => {
|
||||||
|
const interval = setInterval(() => {
|
||||||
|
const localMoment = moment().utcOffset(timezone / 60);
|
||||||
|
const formatedTime = localMoment.format("hh:mm:ss");
|
||||||
|
const day = localMoment.format("dddd")
|
||||||
|
|
||||||
|
setLocalTime(formatedTime);
|
||||||
|
setCurrentDay(day);
|
||||||
|
}, 1000);
|
||||||
|
}, []);
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className='flex flex-col justify-between pt-6 px-4 pb-5 border rounded-lg dark:bg-darkGgray shadow-sm dark:shadow-none'>
|
||||||
|
<p className='flex justify-between items-center'>
|
||||||
|
<span className=''>{currentDay}</span>
|
||||||
|
<span className=''>{localTime}</span>
|
||||||
|
</p>
|
||||||
|
<p className='flex pt-2 font-bold gap-1'>
|
||||||
|
<span>{name}</span>
|
||||||
|
<span className=''><IoNavigateOutline/></span>
|
||||||
|
</p>
|
||||||
|
<p className='py-10 text-8xl font-bold self-center'>{temp}°F</p>
|
||||||
|
<div>
|
||||||
|
<div>
|
||||||
|
<span>{getIcon()}</span>
|
||||||
|
<p className='pt-2 capitalize text-lg'>{description}</p>
|
||||||
|
</div>
|
||||||
|
<p className='flex gap-1'>
|
||||||
|
<span>Low: {minTemp}°F</span>
|
||||||
|
<span>Hight: {maxTemp}°F</span>
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
export default Temperature
|
12
weather_app/app/components/uvIndex/UVI.tsx
Normal file
12
weather_app/app/components/uvIndex/UVI.tsx
Normal file
|
@ -0,0 +1,12 @@
|
||||||
|
"use client"
|
||||||
|
import React from 'react'
|
||||||
|
|
||||||
|
const UVI = () => {
|
||||||
|
return (
|
||||||
|
<div className='flex flex-col gap-5 justify-center dark:bg-darkGgray shadow-sm dark:shadow-none sm-2:col-span-2 px-5 h-[12rem] border rounded-lg'>
|
||||||
|
UVI
|
||||||
|
</div>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
export default UVI
|
33
weather_app/app/components/wind/Wind.tsx
Normal file
33
weather_app/app/components/wind/Wind.tsx
Normal file
|
@ -0,0 +1,33 @@
|
||||||
|
"use client"
|
||||||
|
import { useGlobalContext } from '@/app/context/globalContext'
|
||||||
|
import { Skeleton } from '@/components/ui/skeleton';
|
||||||
|
import React from 'react'
|
||||||
|
import { LuWind } from "react-icons/lu";
|
||||||
|
import { LiaCompass } from "react-icons/lia";
|
||||||
|
|
||||||
|
const Wind = () => {
|
||||||
|
const {forecast} = useGlobalContext();
|
||||||
|
const windSpeed = forecast?.wind?.speed * 2.2369;
|
||||||
|
const windDirection = forecast?.wind?.deg;
|
||||||
|
if(!windSpeed || !windDirection){
|
||||||
|
return <Skeleton className='h-[12rem] w-full col-span-2 md:col-span-full'/>
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className='flex flex-col justify-center gap-5 dark:bg-darkGgray shadow-sm dark:shadow-none sm-2:col-span-2 px-4 h-[12rem] border rounded-lg'>
|
||||||
|
<h2 className='flex items-center gap-1 '><LuWind/> Wind</h2>
|
||||||
|
<div className='flex items-center text-center gap-2 justify-center'>
|
||||||
|
<p className='text-7xl'><LiaCompass/></p>
|
||||||
|
<p className='text-4xl'>{Math.round(windSpeed)}</p>
|
||||||
|
<div className='flex flex-col justify-start text-start'>
|
||||||
|
<p className='text-sm'>Wind</p>
|
||||||
|
<p className='text-xs'>MPH</p>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
</div>
|
||||||
|
|
||||||
|
</div>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
export default Wind
|
262
weather_app/components/ui/carousel.tsx
Normal file
262
weather_app/components/ui/carousel.tsx
Normal file
|
@ -0,0 +1,262 @@
|
||||||
|
"use client"
|
||||||
|
|
||||||
|
import * as React from "react"
|
||||||
|
import useEmblaCarousel, {
|
||||||
|
type UseEmblaCarouselType,
|
||||||
|
} from "embla-carousel-react"
|
||||||
|
import { ArrowLeft, ArrowRight } from "lucide-react"
|
||||||
|
|
||||||
|
import { cn } from "@/lib/utils"
|
||||||
|
import { Button } from "@/components/ui/button"
|
||||||
|
|
||||||
|
type CarouselApi = UseEmblaCarouselType[1]
|
||||||
|
type UseCarouselParameters = Parameters<typeof useEmblaCarousel>
|
||||||
|
type CarouselOptions = UseCarouselParameters[0]
|
||||||
|
type CarouselPlugin = UseCarouselParameters[1]
|
||||||
|
|
||||||
|
type CarouselProps = {
|
||||||
|
opts?: CarouselOptions
|
||||||
|
plugins?: CarouselPlugin
|
||||||
|
orientation?: "horizontal" | "vertical"
|
||||||
|
setApi?: (api: CarouselApi) => void
|
||||||
|
}
|
||||||
|
|
||||||
|
type CarouselContextProps = {
|
||||||
|
carouselRef: ReturnType<typeof useEmblaCarousel>[0]
|
||||||
|
api: ReturnType<typeof useEmblaCarousel>[1]
|
||||||
|
scrollPrev: () => void
|
||||||
|
scrollNext: () => void
|
||||||
|
canScrollPrev: boolean
|
||||||
|
canScrollNext: boolean
|
||||||
|
} & CarouselProps
|
||||||
|
|
||||||
|
const CarouselContext = React.createContext<CarouselContextProps | null>(null)
|
||||||
|
|
||||||
|
function useCarousel() {
|
||||||
|
const context = React.useContext(CarouselContext)
|
||||||
|
|
||||||
|
if (!context) {
|
||||||
|
throw new Error("useCarousel must be used within a <Carousel />")
|
||||||
|
}
|
||||||
|
|
||||||
|
return context
|
||||||
|
}
|
||||||
|
|
||||||
|
const Carousel = React.forwardRef<
|
||||||
|
HTMLDivElement,
|
||||||
|
React.HTMLAttributes<HTMLDivElement> & CarouselProps
|
||||||
|
>(
|
||||||
|
(
|
||||||
|
{
|
||||||
|
orientation = "horizontal",
|
||||||
|
opts,
|
||||||
|
setApi,
|
||||||
|
plugins,
|
||||||
|
className,
|
||||||
|
children,
|
||||||
|
...props
|
||||||
|
},
|
||||||
|
ref
|
||||||
|
) => {
|
||||||
|
const [carouselRef, api] = useEmblaCarousel(
|
||||||
|
{
|
||||||
|
...opts,
|
||||||
|
axis: orientation === "horizontal" ? "x" : "y",
|
||||||
|
},
|
||||||
|
plugins
|
||||||
|
)
|
||||||
|
const [canScrollPrev, setCanScrollPrev] = React.useState(false)
|
||||||
|
const [canScrollNext, setCanScrollNext] = React.useState(false)
|
||||||
|
|
||||||
|
const onSelect = React.useCallback((api: CarouselApi) => {
|
||||||
|
if (!api) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
setCanScrollPrev(api.canScrollPrev())
|
||||||
|
setCanScrollNext(api.canScrollNext())
|
||||||
|
}, [])
|
||||||
|
|
||||||
|
const scrollPrev = React.useCallback(() => {
|
||||||
|
api?.scrollPrev()
|
||||||
|
}, [api])
|
||||||
|
|
||||||
|
const scrollNext = React.useCallback(() => {
|
||||||
|
api?.scrollNext()
|
||||||
|
}, [api])
|
||||||
|
|
||||||
|
const handleKeyDown = React.useCallback(
|
||||||
|
(event: React.KeyboardEvent<HTMLDivElement>) => {
|
||||||
|
if (event.key === "ArrowLeft") {
|
||||||
|
event.preventDefault()
|
||||||
|
scrollPrev()
|
||||||
|
} else if (event.key === "ArrowRight") {
|
||||||
|
event.preventDefault()
|
||||||
|
scrollNext()
|
||||||
|
}
|
||||||
|
},
|
||||||
|
[scrollPrev, scrollNext]
|
||||||
|
)
|
||||||
|
|
||||||
|
React.useEffect(() => {
|
||||||
|
if (!api || !setApi) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
setApi(api)
|
||||||
|
}, [api, setApi])
|
||||||
|
|
||||||
|
React.useEffect(() => {
|
||||||
|
if (!api) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
onSelect(api)
|
||||||
|
api.on("reInit", onSelect)
|
||||||
|
api.on("select", onSelect)
|
||||||
|
|
||||||
|
return () => {
|
||||||
|
api?.off("select", onSelect)
|
||||||
|
}
|
||||||
|
}, [api, onSelect])
|
||||||
|
|
||||||
|
return (
|
||||||
|
<CarouselContext.Provider
|
||||||
|
value={{
|
||||||
|
carouselRef,
|
||||||
|
api: api,
|
||||||
|
opts,
|
||||||
|
orientation:
|
||||||
|
orientation || (opts?.axis === "y" ? "vertical" : "horizontal"),
|
||||||
|
scrollPrev,
|
||||||
|
scrollNext,
|
||||||
|
canScrollPrev,
|
||||||
|
canScrollNext,
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<div
|
||||||
|
ref={ref}
|
||||||
|
onKeyDownCapture={handleKeyDown}
|
||||||
|
className={cn("relative", className)}
|
||||||
|
role="region"
|
||||||
|
aria-roledescription="carousel"
|
||||||
|
{...props}
|
||||||
|
>
|
||||||
|
{children}
|
||||||
|
</div>
|
||||||
|
</CarouselContext.Provider>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
)
|
||||||
|
Carousel.displayName = "Carousel"
|
||||||
|
|
||||||
|
const CarouselContent = React.forwardRef<
|
||||||
|
HTMLDivElement,
|
||||||
|
React.HTMLAttributes<HTMLDivElement>
|
||||||
|
>(({ className, ...props }, ref) => {
|
||||||
|
const { carouselRef, orientation } = useCarousel()
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div ref={carouselRef} className="overflow-hidden">
|
||||||
|
<div
|
||||||
|
ref={ref}
|
||||||
|
className={cn(
|
||||||
|
"flex",
|
||||||
|
orientation === "horizontal" ? "-ml-4" : "-mt-4 flex-col",
|
||||||
|
className
|
||||||
|
)}
|
||||||
|
{...props}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
)
|
||||||
|
})
|
||||||
|
CarouselContent.displayName = "CarouselContent"
|
||||||
|
|
||||||
|
const CarouselItem = React.forwardRef<
|
||||||
|
HTMLDivElement,
|
||||||
|
React.HTMLAttributes<HTMLDivElement>
|
||||||
|
>(({ className, ...props }, ref) => {
|
||||||
|
const { orientation } = useCarousel()
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div
|
||||||
|
ref={ref}
|
||||||
|
role="group"
|
||||||
|
aria-roledescription="slide"
|
||||||
|
className={cn(
|
||||||
|
"min-w-0 shrink-0 grow-0 basis-full",
|
||||||
|
orientation === "horizontal" ? "pl-4" : "pt-4",
|
||||||
|
className
|
||||||
|
)}
|
||||||
|
{...props}
|
||||||
|
/>
|
||||||
|
)
|
||||||
|
})
|
||||||
|
CarouselItem.displayName = "CarouselItem"
|
||||||
|
|
||||||
|
const CarouselPrevious = React.forwardRef<
|
||||||
|
HTMLButtonElement,
|
||||||
|
React.ComponentProps<typeof Button>
|
||||||
|
>(({ className, variant = "outline", size = "icon", ...props }, ref) => {
|
||||||
|
const { orientation, scrollPrev, canScrollPrev } = useCarousel()
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Button
|
||||||
|
ref={ref}
|
||||||
|
variant={variant}
|
||||||
|
size={size}
|
||||||
|
className={cn(
|
||||||
|
"absolute h-8 w-8 rounded-full",
|
||||||
|
orientation === "horizontal"
|
||||||
|
? "-left-12 top-1/2 -translate-y-1/2"
|
||||||
|
: "-top-12 left-1/2 -translate-x-1/2 rotate-90",
|
||||||
|
className
|
||||||
|
)}
|
||||||
|
disabled={!canScrollPrev}
|
||||||
|
onClick={scrollPrev}
|
||||||
|
{...props}
|
||||||
|
>
|
||||||
|
<ArrowLeft className="h-4 w-4" />
|
||||||
|
<span className="sr-only">Previous slide</span>
|
||||||
|
</Button>
|
||||||
|
)
|
||||||
|
})
|
||||||
|
CarouselPrevious.displayName = "CarouselPrevious"
|
||||||
|
|
||||||
|
const CarouselNext = React.forwardRef<
|
||||||
|
HTMLButtonElement,
|
||||||
|
React.ComponentProps<typeof Button>
|
||||||
|
>(({ className, variant = "outline", size = "icon", ...props }, ref) => {
|
||||||
|
const { orientation, scrollNext, canScrollNext } = useCarousel()
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Button
|
||||||
|
ref={ref}
|
||||||
|
variant={variant}
|
||||||
|
size={size}
|
||||||
|
className={cn(
|
||||||
|
"absolute h-8 w-8 rounded-full",
|
||||||
|
orientation === "horizontal"
|
||||||
|
? "-right-12 top-1/2 -translate-y-1/2"
|
||||||
|
: "-bottom-12 left-1/2 -translate-x-1/2 rotate-90",
|
||||||
|
className
|
||||||
|
)}
|
||||||
|
disabled={!canScrollNext}
|
||||||
|
onClick={scrollNext}
|
||||||
|
{...props}
|
||||||
|
>
|
||||||
|
<ArrowRight className="h-4 w-4" />
|
||||||
|
<span className="sr-only">Next slide</span>
|
||||||
|
</Button>
|
||||||
|
)
|
||||||
|
})
|
||||||
|
CarouselNext.displayName = "CarouselNext"
|
||||||
|
|
||||||
|
export {
|
||||||
|
type CarouselApi,
|
||||||
|
Carousel,
|
||||||
|
CarouselContent,
|
||||||
|
CarouselItem,
|
||||||
|
CarouselPrevious,
|
||||||
|
CarouselNext,
|
||||||
|
}
|
28
weather_app/components/ui/progress.tsx
Normal file
28
weather_app/components/ui/progress.tsx
Normal file
|
@ -0,0 +1,28 @@
|
||||||
|
"use client"
|
||||||
|
|
||||||
|
import * as React from "react"
|
||||||
|
import * as ProgressPrimitive from "@radix-ui/react-progress"
|
||||||
|
|
||||||
|
import { cn } from "@/lib/utils"
|
||||||
|
|
||||||
|
const Progress = React.forwardRef<
|
||||||
|
React.ElementRef<typeof ProgressPrimitive.Root>,
|
||||||
|
React.ComponentPropsWithoutRef<typeof ProgressPrimitive.Root>
|
||||||
|
>(({ className, value, ...props }, ref) => (
|
||||||
|
<ProgressPrimitive.Root
|
||||||
|
ref={ref}
|
||||||
|
className={cn(
|
||||||
|
"relative h-2 w-full overflow-hidden rounded-full bg-secondary",
|
||||||
|
className
|
||||||
|
)}
|
||||||
|
{...props}
|
||||||
|
>
|
||||||
|
<ProgressPrimitive.Indicator
|
||||||
|
className="h-2 w-2 flex-1 bg-white transition-all shadow-lg shadow-white rounded-full ring-1 dark:ring-gray-500"
|
||||||
|
style={{ marginLeft: `calc(${value}% - 1rem)` }}
|
||||||
|
/>
|
||||||
|
</ProgressPrimitive.Root>
|
||||||
|
))
|
||||||
|
Progress.displayName = ProgressPrimitive.Root.displayName
|
||||||
|
|
||||||
|
export { Progress }
|
15
weather_app/components/ui/skeleton.tsx
Normal file
15
weather_app/components/ui/skeleton.tsx
Normal file
|
@ -0,0 +1,15 @@
|
||||||
|
import { cn } from "@/lib/utils"
|
||||||
|
|
||||||
|
function Skeleton({
|
||||||
|
className,
|
||||||
|
...props
|
||||||
|
}: React.HTMLAttributes<HTMLDivElement>) {
|
||||||
|
return (
|
||||||
|
<div
|
||||||
|
className={cn("animate-pulse rounded-md bg-muted", className)}
|
||||||
|
{...props}
|
||||||
|
/>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
export { Skeleton }
|
Loading…
Add table
Reference in a new issue