+
+export const Default: Story = {}
diff --git a/src/features/weather/components/weather_specific_day/presenter.tsx b/src/features/weather/components/weather_specific_day/presenter.tsx
new file mode 100644
index 0000000..af156db
--- /dev/null
+++ b/src/features/weather/components/weather_specific_day/presenter.tsx
@@ -0,0 +1,17 @@
+import { ReactNode } from "react"
+
+import * as styles from "./presenter.css"
+
+export type PresenterProps = {
+ locationTitleNode: ReactNode
+ weatherInfoNode: ReactNode
+}
+
+export const Presenter = ({ ...props }: PresenterProps) => {
+ return (
+
+ {props.locationTitleNode}
+ {props.weatherInfoNode}
+
+ )
+}
diff --git a/src/features/weather/hooks/index.ts b/src/features/weather/hooks/index.ts
index 03abec9..a0a4bae 100644
--- a/src/features/weather/hooks/index.ts
+++ b/src/features/weather/hooks/index.ts
@@ -1,2 +1,3 @@
export * from "./useQueryMatchedLocation"
export * from "./useQueryWeatherForecast"
+export * from "./useWeatherParams"
diff --git a/src/features/weather/hooks/useWeatherParams/index.test.ts b/src/features/weather/hooks/useWeatherParams/index.test.ts
new file mode 100644
index 0000000..36584a2
--- /dev/null
+++ b/src/features/weather/hooks/useWeatherParams/index.test.ts
@@ -0,0 +1,38 @@
+import { renderHook } from "@testing-library/react"
+import { useSearchParams } from "react-router-dom"
+import { describe, expect, it, Mock, vitest } from "vitest"
+
+import { format } from "@/utils"
+
+import { useWeatherParams } from "./"
+
+vitest.mock("react-router-dom", () => ({
+ useSearchParams: vitest.fn(),
+}))
+
+describe("useWeatherParams", () => {
+ it("初期値を返すこと", () => {
+ const setSearchParams = vitest.fn()
+ ;(useSearchParams as Mock).mockReturnValue([new URLSearchParams(), setSearchParams])
+
+ const { result } = renderHook(() => useWeatherParams({ defaultLocation: "Tokyo" }))
+
+ expect(result.current.location).toBe("Tokyo")
+ expect(result.current.date).toBe(format(new Date(), "yyyy-MM-dd"))
+ expect(result.current.setSearchParams).toBe(setSearchParams)
+ })
+
+ it("引数で渡した値が正常に処理される", () => {
+ const setSearchParams = vitest.fn()
+ const searchParams = new URLSearchParams()
+ searchParams.set("location", "Sapporo")
+ searchParams.set("date", "2024-09-30")
+ ;(useSearchParams as Mock).mockReturnValue([searchParams, setSearchParams])
+
+ const { result } = renderHook(() => useWeatherParams({ defaultLocation: "Tokyo" }))
+
+ expect(result.current.location).toBe("Sapporo")
+ expect(result.current.date).toBe("2024-09-30")
+ expect(result.current.setSearchParams).toBe(setSearchParams)
+ })
+})
diff --git a/src/features/weather/hooks/useWeatherParams/index.ts b/src/features/weather/hooks/useWeatherParams/index.ts
new file mode 100644
index 0000000..202f358
--- /dev/null
+++ b/src/features/weather/hooks/useWeatherParams/index.ts
@@ -0,0 +1,29 @@
+import { format } from "date-fns"
+import { type SetURLSearchParams, useSearchParams } from "react-router-dom"
+
+type WeatherAPiParams = {
+ location: string
+ date: string
+ setSearchParams: SetURLSearchParams
+}
+
+export function useWeatherParams({
+ defaultLocation = "Tokyo",
+}: {
+ defaultLocation?: string
+}): WeatherAPiParams {
+ const [searchParams, setSearchParams] = useSearchParams()
+
+ const location = searchParams.get("location") || defaultLocation
+ const dateParam = searchParams.get("date")
+
+ const date = dateParam
+ ? format(new Date(dateParam), "yyyy-MM-dd")
+ : format(new Date(), "yyyy-MM-dd")
+
+ return {
+ location,
+ date,
+ setSearchParams,
+ }
+}
diff --git a/src/features/weather/utils/transform/index.ts b/src/features/weather/utils/transform/index.ts
index a411bf8..fd5c11f 100644
--- a/src/features/weather/utils/transform/index.ts
+++ b/src/features/weather/utils/transform/index.ts
@@ -1,3 +1,4 @@
+import { Day } from "@/features/weather/models"
import { Current } from "@/features/weather/models/common"
export const transformWeatherInfoForCurrent = (current: Current) => {
@@ -23,3 +24,24 @@ export const transformWeatherInfoForCurrent = (current: Current) => {
"Gusts (kph)": current.gust_kph,
}
}
+
+export const transformWeatherInfoForSpecificDay = (day: Day) => {
+ return {
+ "Max Temperature (°C)": day.maxtemp_c,
+ "Max Temperature (°F)": day.maxtemp_f,
+ "Min Temperature (°C)": day.mintemp_c,
+ "Min Temperature (°F)": day.mintemp_f,
+ "Average Temperature (°C)": day.avgtemp_c,
+ "Average Temperature (°F)": day.avgtemp_f,
+ "Max Wind Speed (mph)": day.maxwind_mph,
+ "Max Wind Speed (kph)": day.maxwind_kph,
+ "Total Precipitation (mm)": day.totalprecip_mm,
+ "Total Precipitation (in)": day.totalprecip_in,
+ "Average Visibility (km)": day.avgvis_km,
+ "Average Visibility (miles)": day.avgvis_miles,
+ "Average Humidity (%)": day.avghumidity,
+ "Chance of Rain (%)": day.daily_chance_of_rain,
+ "Chance of Snow (%)": day.daily_chance_of_snow,
+ "UV Index": day.uv,
+ }
+}
diff --git a/src/hooks/index.ts b/src/hooks/index.ts
index da31530..94ed230 100644
--- a/src/hooks/index.ts
+++ b/src/hooks/index.ts
@@ -1 +1,2 @@
export * from "./useDebouncedValue"
+export * from "./useLinkProps"
diff --git a/src/hooks/useLinkProps/index.test.ts b/src/hooks/useLinkProps/index.test.ts
new file mode 100644
index 0000000..170c0b6
--- /dev/null
+++ b/src/hooks/useLinkProps/index.test.ts
@@ -0,0 +1,75 @@
+import { renderHook } from "@testing-library/react"
+import { useLocation } from "react-router-dom"
+import { describe, expect, MockedFunction, test, vitest } from "vitest"
+
+import { useLinkProps } from "./"
+
+vitest.mock("react-router-dom", () => ({
+ useLocation: vitest.fn(),
+}))
+
+describe("useLinkProps", () => {
+ test("paramが追加されること", () => {
+ // useLocationのモックを設定
+ const mockUseLocation = useLocation as MockedFunction
+ mockUseLocation.mockReturnValue({ search: "?existingParam=value" } as ReturnType<
+ typeof useLocation
+ >)
+
+ const { result } = renderHook(() => useLinkProps())
+
+ const linkProps = result.current.generateLinkProps({
+ pathname: "/test",
+ newParams: { newParam: "newValue" },
+ })
+
+ expect(linkProps).toEqual({
+ to: {
+ pathname: "/test",
+ search: "?existingParam=value&newParam=newValue",
+ },
+ })
+ })
+
+ test("指定したparamが削除されること", () => {
+ const mockUseLocation = useLocation as MockedFunction
+ mockUseLocation.mockReturnValue({
+ search: "?param1=value1¶m2=value2¶m3=value3",
+ } as ReturnType)
+
+ const { result } = renderHook(() => useLinkProps())
+
+ const linkProps = result.current.generateLinkProps({
+ pathname: "/test",
+ removeParams: ["param2"],
+ })
+
+ expect(linkProps).toEqual({
+ to: {
+ pathname: "/test",
+ search: "?param1=value1¶m3=value3",
+ },
+ })
+ })
+
+ test("存在するparamの場合は上書きをすること", () => {
+ const mockUseLocation = useLocation as MockedFunction
+ mockUseLocation.mockReturnValue({ search: "?existingParam=oldValue" } as ReturnType<
+ typeof useLocation
+ >)
+
+ const { result } = renderHook(() => useLinkProps())
+
+ const linkProps = result.current.generateLinkProps({
+ pathname: "/test",
+ newParams: { existingParam: "newValue" },
+ })
+
+ expect(linkProps).toEqual({
+ to: {
+ pathname: "/test",
+ search: "?existingParam=newValue",
+ },
+ })
+ })
+})
diff --git a/src/hooks/useLinkProps/index.ts b/src/hooks/useLinkProps/index.ts
new file mode 100644
index 0000000..1efd049
--- /dev/null
+++ b/src/hooks/useLinkProps/index.ts
@@ -0,0 +1,42 @@
+import { type LinkProps as ReactRouterLinkProps, useLocation } from "react-router-dom"
+
+export type LinkProps = ReactRouterLinkProps
+
+type GenerateLinkPropsOptions = {
+ pathname: string
+ newParams?: Record
+ removeParams?: string[]
+}
+
+export function useLinkProps() {
+ const location = useLocation()
+
+ function generateLinkProps({
+ pathname,
+ newParams = {},
+ removeParams = [],
+ }: GenerateLinkPropsOptions): LinkProps {
+ const currentParams = new URLSearchParams(location.search)
+ const newParamsObj = new URLSearchParams(newParams)
+
+ removeParams.forEach((param) => currentParams.delete(param))
+
+ for (const [key, value] of newParamsObj.entries()) {
+ if (currentParams.has(key) && currentParams.get(key) === value) {
+ continue
+ }
+ currentParams.set(key, value)
+ }
+
+ const mergedSearch = currentParams.toString()
+
+ return {
+ to: {
+ pathname,
+ search: mergedSearch ? `?${mergedSearch}` : "",
+ },
+ }
+ }
+
+ return { generateLinkProps }
+}
diff --git a/src/styles/vars.css.ts b/src/styles/vars.css.ts
index 3b46f16..391f4e3 100644
--- a/src/styles/vars.css.ts
+++ b/src/styles/vars.css.ts
@@ -45,6 +45,10 @@ export const vars = createGlobalTheme(":root", {
inverse: "#FFFFFF",
error: "#8B0000",
},
+ button: {
+ primary: "#000000",
+ text: "#FFFFFF",
+ },
weather: {
sunny: {
background: "#FACC15",