diff --git a/frontend/src/components/MaterialSidebar.tsx b/frontend/src/components/MaterialSidebar.tsx index 8428c09..e686a61 100644 --- a/frontend/src/components/MaterialSidebar.tsx +++ b/frontend/src/components/MaterialSidebar.tsx @@ -489,7 +489,7 @@ const MaterialSidebar: FC = () => {
- +
= ({ post, sp }) => { }, [baseTags]) return ( - + = ({ posts, onClick }) => { ) return ( - +
diff --git a/frontend/src/components/layout/SidebarComponent.tsx b/frontend/src/components/layout/SidebarComponent.tsx index 779755e..922b787 100644 --- a/frontend/src/components/layout/SidebarComponent.tsx +++ b/frontend/src/components/layout/SidebarComponent.tsx @@ -1,19 +1,307 @@ import { motion } from 'framer-motion' import { Helmet } from 'react-helmet-async' -import type { FC, ReactNode } from 'react' +import { cn } from '@/lib/utils' -type Props = { children: ReactNode } +import type { CSSProperties, FC, MouseEvent, PointerEvent, ReactNode } from 'react' +import { useCallback, useEffect, useRef, useState } from 'react' + +type SidebarSide = 'left' | 'right' + +type Props = { + children: ReactNode + className?: string + maxWidth?: number + onWidthChange?: (width: number) => void + sidebarKey: string + side?: SidebarSide } + +type ResizeState = { + pointerId: number + startWidth: number + startX: number + side: SidebarSide } + +type PendingPressState = ResizeState & { startY: number } + +const DEFAULT_SIDEBAR_WIDTH = 256 +const MIN_SIDEBAR_WIDTH = 192 +const MIN_MAIN_WIDTH = 360 +const MAX_VIEWPORT_WIDTH_RATIO = .8 +const LONG_PRESS_DELAY_MS = 350 +const TOUCH_MOVE_THRESHOLD_PX = 8 -const SidebarComponent: FC = ({ children }) => ( - - - - + + - {children} - ) +
+ {children} +
-export default SidebarComponent \ No newline at end of file +
+ ) +} + +export default SidebarComponent diff --git a/frontend/src/pages/theatres/TheatreDetailPage.tsx b/frontend/src/pages/theatres/TheatreDetailPage.tsx index e4db3d2..6efb2cc 100644 --- a/frontend/src/pages/theatres/TheatreDetailPage.tsx +++ b/frontend/src/pages/theatres/TheatreDetailPage.tsx @@ -10,6 +10,7 @@ import PrefetchLink from '@/components/PrefetchLink' import TagLink from '@/components/TagLink' import FieldError from '@/components/common/FieldError' import { useDialogue } from '@/components/dialogues/DialogueProvider' +import SidebarComponent from '@/components/layout/SidebarComponent' import { Button } from '@/components/ui/button' import { SITE_TITLE } from '@/config' import { CATEGORIES, CATEGORY_NAMES } from '@/consts' @@ -52,6 +53,8 @@ const INITIAL_WEIGHTS: TheatrePostSelectionWeights = const LAYOUT_STORAGE_KEY = 'theatre-layout-mode' const TAG_FLOW_STORAGE_KEY = 'theatre-tag-flow' +const MIN_MAIN_WIDTH = 360 +const SIDEBAR_GAP_WIDTH = 16 const LAYOUT_LABELS: Record = { threeColumns: '3 列', @@ -211,11 +214,16 @@ const TheatreDetailPage: FC = ({ user }: Props) => { const commentsRef = useRef (null) const embedRef = useRef (null) const loadingRef = useRef (false) + const sidebarContainerRef = useRef (null) const theatreInfoRef = useRef (INITIAL_THEATRE_INFO) const theatreInfoReceivedAtRef = useRef (performance.now ()) const videoLengthRef = useRef (0) const lastCommentNoRef = useRef (0) + const [leftSidebarWidth, setLeftSidebarWidth] = useState (256) + const [rightSidebarWidth, setRightSidebarWidth] = useState (256) + const [sidebarContainerWidth, setSidebarContainerWidth] = useState ( + () => typeof window === 'undefined' ? 0 : window.innerWidth) const [comments, setComments] = useState ([]) const [content, setContent] = useState ('') const [editingPost, setEditingPost] = useState (null) @@ -324,6 +332,17 @@ const TheatreDetailPage: FC = ({ user }: Props) => { lastCommentNoRef.current = comments[0]?.no ?? 0 }, [comments]) + useEffect (() => { + const updateSidebarContainerWidth = () => { + const nextWidth = sidebarContainerRef.current?.clientWidth ?? window.innerWidth + setSidebarContainerWidth (nextWidth) + } + + updateSidebarContainerWidth () + window.addEventListener ('resize', updateSidebarContainerWidth) + return () => window.removeEventListener ('resize', updateSidebarContainerWidth) + }, []) + useEffect (() => { if (!(id)) return @@ -639,6 +658,26 @@ const TheatreDetailPage: FC = ({ user }: Props) => { if (status >= 400) return + const singleSidebarMaxWidth = Math.max ( + 192, + sidebarContainerWidth - MIN_MAIN_WIDTH - SIDEBAR_GAP_WIDTH) + const leftSidebarMaxWidth = layoutMode === 'threeColumns' + ? Math.max ( + 192, + sidebarContainerWidth + - rightSidebarWidth + - MIN_MAIN_WIDTH + - SIDEBAR_GAP_WIDTH * 2) + : singleSidebarMaxWidth + const rightSidebarMaxWidth = layoutMode === 'threeColumns' + ? Math.max ( + 192, + sidebarContainerWidth + - leftSidebarWidth + - MIN_MAIN_WIDTH + - SIDEBAR_GAP_WIDTH * 2) + : singleSidebarMaxWidth + const tagPanel = (
@@ -806,30 +845,26 @@ const TheatreDetailPage: FC = ({ user }: Props) => { {theatre && {`${ theatreTitle } | ${ SITE_TITLE }`}} -
+
{layoutMode !== 'tagsBottom' && ( - -
- {tagPanel} -
-
)} + + {tagPanel} + )} -
+ className="order-1 min-w-0 flex-1 space-y-4 md:order-none + md:min-w-[360px] md:overflow-y-auto"> +
- {commentsPanel} - {participantsPanel} - )} + +
+ {commentsPanel} + {participantsPanel} +
+
)}
) }