diff --git a/frontend/src/components/Header.stories.tsx b/frontend/src/components/Header.stories.tsx index c872bfe..679bdb6 100644 --- a/frontend/src/components/Header.stories.tsx +++ b/frontend/src/components/Header.stories.tsx @@ -61,6 +61,11 @@ const meta = { ), ], argTypes: { + variant: { + control: { type: 'radio' }, + options: ['full', 'logoOnly'], + description: '表示バリアント(full: 全機能表示 / logoOnly: ロゴのみ)', + }, mode: { control: { type: 'radio' }, options: ['view', 'edit'], @@ -85,10 +90,24 @@ const meta = { } satisfies Meta; export default meta; -type Story = StoryObj; +type Story = StoryObj; + +export const LogoOnly: Story = { + args: { + variant: 'logoOnly', + }, + parameters: { + docs: { + description: { + story: 'ロゴのみを表示するシンプルなヘッダー。ホーム画面などで使用されます。', + }, + }, + }, +}; export const Default: Story = { args: { + variant: 'full', trip: demoTrip, pages: demoPages, mode: 'view', @@ -110,6 +129,7 @@ export const Default: Story = { export const EditMode: Story = { args: { + variant: 'full', trip: demoTrip, pages: demoPages, mode: 'edit', @@ -131,6 +151,7 @@ export const EditMode: Story = { export const SinglePage: Story = { args: { + variant: 'full', trip: { id: 1, title: '日帰り温泉ツアー', @@ -155,6 +176,7 @@ export const SinglePage: Story = { export const EmptyPages: Story = { args: { + variant: 'full', trip: { id: 1, title: '新しい旅行計画', @@ -179,6 +201,7 @@ export const EmptyPages: Story = { export const WithCustomClass: Story = { args: { + variant: 'full', trip: demoTrip, pages: demoPages, mode: 'view', @@ -201,6 +224,7 @@ export const WithCustomClass: Story = { export const ScrolledState: Story = { args: { + variant: 'full', trip: demoTrip, pages: demoPages, mode: 'view', @@ -225,7 +249,7 @@ export const ScrolledState: Story = { const scrollContainerRef = useRef(null); return (
- +

スクロールしてヘッダーの変化を確認

diff --git a/frontend/src/components/Header.tsx b/frontend/src/components/Header.tsx index 9c746c5..ca2e119 100644 --- a/frontend/src/components/Header.tsx +++ b/frontend/src/components/Header.tsx @@ -1,4 +1,4 @@ -import { type Dispatch, type SetStateAction, useCallback, useEffect, useRef, useState } from 'react'; +import React, { type Dispatch, type SetStateAction, useCallback, useEffect, useRef, useState } from 'react'; import editScheduleIcon from '@/assets/icons/edit-schedule-white.svg'; import eyeSolidIcon from '@/assets/icons/eye-solid-white.svg'; import penToSquareSolidIcon from '@/assets/icons/pen-to-square-solid-white.svg'; @@ -12,7 +12,14 @@ import { Badge } from './ui/badge'; import { Button } from './ui/button'; import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from './ui/select'; -type HeaderProps = { +type HeaderBaseProps = React.ComponentProps<'div'>; + +type HeaderLogoOnlyProps = HeaderBaseProps & { + variant: 'logoOnly'; +}; + +type HeaderFullProps = HeaderBaseProps & { + variant: 'full'; trip: Trip; pages: Page[]; mode?: 'view' | 'edit'; @@ -20,12 +27,28 @@ type HeaderProps = { onSelectPage: (pageId: Page['id']) => void; scrollContainerRef: React.RefObject; setMode: Dispatch>; - className?: string; }; +type HeaderProps = HeaderLogoOnlyProps | HeaderFullProps; + const transitionClassNames = 'duration-300 ease-in-out'; -export function Header({ +function HeaderLogoOnly({ className, ...props }: HeaderLogoOnlyProps) { + return ( +

+ +
+ ); +} + +function HeaderFull({ pages, trip, mode = 'view', @@ -34,7 +57,8 @@ export function Header({ setMode, className, scrollContainerRef, -}: HeaderProps) { + ...props +}: Omit) { const [isScrolled, setIsScrolled] = useState(false); const [editPageDialogOpen, setEditPageDialogOpen] = useState(false); const [addPageDialogOpen, setAddPageDialogOpen] = useState(false); @@ -89,8 +113,11 @@ export function Header({ 'sticky top-0 right-0 left-0 z-10 flex w-full flex-col justify-center gap-1 bg-teal-50/80 px-6 py-2 backdrop-blur-sm', className )} + {...props} > - +
+ +
{/* 左カラム */}
; + } + return ; +} + type HeaderButtonBaseProps = React.ComponentProps & { isScrolled: boolean; iconSrc: string; diff --git a/frontend/src/components/HeaderSkeleton.tsx b/frontend/src/components/HeaderSkeleton.tsx index b9ecb99..a5b7852 100644 --- a/frontend/src/components/HeaderSkeleton.tsx +++ b/frontend/src/components/HeaderSkeleton.tsx @@ -16,7 +16,7 @@ export function HeaderSkeleton({ className }: HeaderSkeletonProps) { )} > {/* Logo */} -
+
diff --git a/frontend/src/components/Logo.tsx b/frontend/src/components/Logo.tsx index 5c01d6a..068a1a1 100644 --- a/frontend/src/components/Logo.tsx +++ b/frontend/src/components/Logo.tsx @@ -9,7 +9,11 @@ interface LogoProps { export function Logo({ size = 'medium', className }: LogoProps) { return ( - + void; + onCreated?: (trip: Trip) => void; +} + +export const AddTripDialog = ({ open, onOpenChange, onCreated }: AddTripDialogProps) => { + const [title, setTitle] = useState(''); + const [detail, setDetail] = useState(''); + const [peopleNum, setPeopleNum] = useState(undefined); + const { createTrip } = useCreateTrip(); + + const handleSubmit = async () => { + if (!title.trim()) { + return; + } + + const newTrip = await createTrip({ + title: title.trim(), + detail: detail.trim() || undefined, + peopleNum: peopleNum, + }); + + if (newTrip) { + onCreated?.(newTrip); + onOpenChange(false); + } + }; + + const handleOpenChange = (open: boolean) => { + if (!open) { + setTitle(''); + setDetail(''); + setPeopleNum(undefined); + } + onOpenChange(open); + }; + + return ( + + + + 旅程を追加 + + +
+
+ + setTitle(e.target.value)} + placeholder='例: 箱根温泉旅行' + /> +
+ +
+ +