diff --git a/src/app/pages/specific_day.tsx b/src/app/pages/specific_day.tsx index 8a57a2d..8be9c1a 100644 --- a/src/app/pages/specific_day.tsx +++ b/src/app/pages/specific_day.tsx @@ -1,12 +1,30 @@ +import { Suspense } from "react" +import { ErrorBoundary } from "react-error-boundary" + +import { HeadGroup } from "@/components/ui/HeadGroup" import { Section } from "@/components/ui/Section" +import * as BackButton from "@/features/weather/components/back_button" import * as LocationInput from "@/features/weather/components/location_input" +import * as SpecificDate from "@/features/weather/components/specific_date" +import * as WeatherSpecificDay from "@/features/weather/components/weather_specific_day" import { LocationContextProvider } from "@/features/weather/providers/location/provider" export default function SpecificDay() { return (
- + + + + + + + + + }> + + +
) diff --git a/src/components/ui/Button/index.css.ts b/src/components/ui/Button/index.css.ts new file mode 100644 index 0000000..db2911c --- /dev/null +++ b/src/components/ui/Button/index.css.ts @@ -0,0 +1,36 @@ +import { recipe, RecipeVariants } from "@vanilla-extract/recipes" + +import { vars } from "@/styles/vars.css" + +export const button = recipe({ + base: { + display: "inline-flex", + justifyContent: "center", + alignItems: "center", + borderRadius: vars.rounded.md, + fontSize: "0.875rem", + lineHeight: "1.25rem", + fontWeight: 500, + whiteSpace: "nowrap", + color: vars.colors.button.text, + cursor: "pointer", + ":hover": { + opacity: ".7", + }, + }, + variants: { + color: { + primary: { background: vars.colors.button.primary }, + }, + size: { + sm: { padding: "0 0.75rem", height: "2.25rem" }, + md: { padding: "0 1rem", height: "2.5rem" }, + }, + }, + defaultVariants: { + color: "primary", + size: "md", + }, +}) + +export type ButtonVariants = RecipeVariants diff --git a/src/components/ui/Button/index.stories.tsx b/src/components/ui/Button/index.stories.tsx new file mode 100644 index 0000000..af76067 --- /dev/null +++ b/src/components/ui/Button/index.stories.tsx @@ -0,0 +1,56 @@ +import type { Meta, StoryObj } from "@storybook/react" +import { expect, fn, userEvent, within } from "@storybook/test" + +import { Button } from "." + +const meta = { + title: "UI/Button", + component: Button, + tags: ["autodocs"], + parameters: { + layout: "centered", + }, + args: { children: "button", onClick: fn() }, +} satisfies Meta + +export default meta +type Story = StoryObj + +const testButton: Story["play"] = async ({ args, canvasElement, step }) => { + const canvas = within(canvasElement) + const button = await canvas.findByRole("button") + + await step("ボタンをクリックできること", async () => { + await userEvent.click(button) + await expect(args.onClick).toHaveBeenCalled() + }) +} + +const testDisabledButton: Story["play"] = async ({ args, canvasElement, step }) => { + const canvas = within(canvasElement) + const button = await canvas.findByRole("button") + + await step("ボタンがクリックできないこと", async () => { + await userEvent.click(button, { pointerEventsCheck: 0 }) + await expect(args.onClick).not.toHaveBeenCalled() + }) +} + +export const Default: Story = { + play: testButton, +} + +export const Disabled: Story = { + args: { + disabled: true, + }, + play: testDisabledButton, +} + +export const Small: Story = { + args: { size: "sm" }, +} + +export const Medium: Story = { + args: { size: "md" }, +} diff --git a/src/components/ui/Button/index.tsx b/src/components/ui/Button/index.tsx new file mode 100644 index 0000000..e884cd0 --- /dev/null +++ b/src/components/ui/Button/index.tsx @@ -0,0 +1,14 @@ +import clsx from "clsx" +import type { ComponentPropsWithoutRef } from "react" +import { forwardRef } from "react" + +import * as styles from "./index.css" + +type ButtonProps = ComponentPropsWithoutRef<"button"> & styles.ButtonVariants + +export const Button = forwardRef(function ButtonBase( + { color, size, className, ...props }, + ref, +) { + return + + ), + }, +} satisfies Meta + +export default meta +type Story = StoryObj + +export const Default: Story = {} diff --git a/src/components/ui/HeadGroup/index.tsx b/src/components/ui/HeadGroup/index.tsx new file mode 100644 index 0000000..0866080 --- /dev/null +++ b/src/components/ui/HeadGroup/index.tsx @@ -0,0 +1,13 @@ +import clsx from "clsx" +import React from "react" + +import * as styles from "./index.css" + +type Props = { + children: React.ReactNode + className?: string +} + +export const HeadGroup = ({ children, className }: Props) => { + return
{children}
+} diff --git a/src/features/weather/components/back_button/container.tsx b/src/features/weather/components/back_button/container.tsx new file mode 100644 index 0000000..a6c4e97 --- /dev/null +++ b/src/features/weather/components/back_button/container.tsx @@ -0,0 +1,23 @@ +import { ArrowLeft } from "lucide-react" +import { useNavigate } from "react-router-dom" + +import { useLinkProps } from "@/hooks" + +import { Presenter, PresenterProps } from "./presenter" + +export const Container = () => { + const navigate = useNavigate() + const { generateLinkProps } = useLinkProps() + + const presenterProps = { + children: ( + <> + + 戻る + + ), + onClick: () => navigate(generateLinkProps({ pathname: "/", removeParams: ["date"] }).to), + } satisfies PresenterProps + + return +} diff --git a/src/features/weather/components/back_button/index.ts b/src/features/weather/components/back_button/index.ts new file mode 100644 index 0000000..033bd48 --- /dev/null +++ b/src/features/weather/components/back_button/index.ts @@ -0,0 +1,2 @@ +export * from "./container" +export * from "./presenter" diff --git a/src/features/weather/components/back_button/presenter.css.ts b/src/features/weather/components/back_button/presenter.css.ts new file mode 100644 index 0000000..235a290 --- /dev/null +++ b/src/features/weather/components/back_button/presenter.css.ts @@ -0,0 +1,6 @@ +import { style } from "@vanilla-extract/css" + +export const module = style({ + display: "flex", + gap: "0.5rem", +}) diff --git a/src/features/weather/components/back_button/presenter.stories.tsx b/src/features/weather/components/back_button/presenter.stories.tsx new file mode 100644 index 0000000..dec9b2e --- /dev/null +++ b/src/features/weather/components/back_button/presenter.stories.tsx @@ -0,0 +1,19 @@ +import { Meta, StoryObj } from "@storybook/react" + +import { Presenter } from "./presenter" + +const meta = { + title: "Features/Weather/BackButton/Presenter", + tags: ["autodocs"], + component: Presenter, + args: { + children: "戻る", + }, +} satisfies Meta +export default meta + +type Story = StoryObj + +export const Default: Story = { + args: {}, +} diff --git a/src/features/weather/components/back_button/presenter.tsx b/src/features/weather/components/back_button/presenter.tsx new file mode 100644 index 0000000..b1d03da --- /dev/null +++ b/src/features/weather/components/back_button/presenter.tsx @@ -0,0 +1,11 @@ +import { ComponentProps } from "react" + +import { Button } from "@/components/ui/Button" + +import * as styles from "./presenter.css" + +export type PresenterProps = ComponentProps + +export const Presenter = ({ ...props }: PresenterProps) => { + return