このコミットが含まれているのは:
2026-07-04 17:40:55 +09:00
コミット 3f2cddcd07
7個のファイルの変更101行の追加57行の削除
+17 -19
ファイルの表示
@@ -488,25 +488,23 @@ const MaterialSidebar: FC = () => {
</div> </div>
</div> </div>
<div className="hidden md:block"> <SidebarComponent sidebarKey="materials" className="hidden md:block">
<SidebarComponent sidebarKey="materials"> <div className="space-y-4">
<div className="space-y-4"> <MaterialFilterButtons
<MaterialFilterButtons materialFilter={materialFilter}
materialFilter={materialFilter} onChange={handleFilterChange}/>
onChange={handleFilterChange}/> {isLoading && (
{isLoading && ( <p className="text-sm text-neutral-500 dark:text-stone-400"></p>)}
<p className="text-sm text-neutral-500 dark:text-stone-400"></p>)} {isError && (
{isError && ( <p className="text-sm text-red-600 dark:text-red-300">
<p className="text-sm text-red-600 dark:text-red-300">
</p>)}
</p>)} {(!isLoading && !isError) && (
{(!isLoading && !isError) && ( <ul>
<ul> {renderDesktopTree (visibleRootTags)}
{renderDesktopTree (visibleRootTags)} </ul>)}
</ul>)} </div>
</div> </SidebarComponent>
</SidebarComponent>
</div>
</>) </>)
} }
+6 -3
ファイルの表示
@@ -146,10 +146,13 @@ const DropSlot = ({ cat }: { cat: Category }) => {
} }
type Props = { post: Post; sp?: boolean } type Props = {
className?: string
post: Post
sp?: boolean }
const TagDetailSidebar: FC<Props> = ({ post, sp }) => { const TagDetailSidebar: FC<Props> = ({ className, post, sp }) => {
sp = Boolean (sp) sp = Boolean (sp)
const qc = useQueryClient () const qc = useQueryClient ()
@@ -278,7 +281,7 @@ const TagDetailSidebar: FC<Props> = ({ post, sp }) => {
}, [baseTags]) }, [baseTags])
return ( return (
<SidebarComponent sidebarKey="post-detail-tags"> <SidebarComponent sidebarKey="post-detail-tags" className={className}>
<TagSearch/> <TagSearch/>
<DndContext <DndContext
sensors={sensors} sensors={sensors}
+33 -8
ファイルの表示
@@ -13,6 +13,16 @@ const sidebarRoot = (container: HTMLElement): Element =>
container.querySelector ('[style*="--sidebar-width"]')! container.querySelector ('[style*="--sidebar-width"]')!
const sidebarTree = (props: Partial<Parameters<typeof SidebarComponent>[0]>) => (
<HelmetProvider>
<div data-sidebar-container>
<SidebarComponent sidebarKey="test-sidebar" {...props}>
Sidebar body
</SidebarComponent>
</div>
</HelmetProvider>)
const renderSidebar = ( const renderSidebar = (
props: Partial<Parameters<typeof SidebarComponent>[0]> = {}, props: Partial<Parameters<typeof SidebarComponent>[0]> = {},
containerWidth = 800, containerWidth = 800,
@@ -24,14 +34,7 @@ const renderSidebar = (
}, },
}) })
const rendered = render ( const rendered = render (sidebarTree (props))
<HelmetProvider>
<div data-sidebar-container>
<SidebarComponent sidebarKey="test-sidebar" {...props}>
Sidebar body
</SidebarComponent>
</div>
</HelmetProvider>)
return rendered return rendered
} }
@@ -96,4 +99,26 @@ describe ('SidebarComponent', () => {
expect (localStorage.getItem ('sidebar:test-sidebar:right:width')).toBeNull () expect (localStorage.getItem ('sidebar:test-sidebar:right:width')).toBeNull ()
expect (localStorage.getItem ('sidebar:other-sidebar:right:width')).toBe ('480') expect (localStorage.getItem ('sidebar:other-sidebar:right:width')).toBe ('480')
}) })
it ('clamps current width on maxWidth changes without re-reading storage', async () => {
localStorage.setItem ('sidebar:test-sidebar:left:width', '420')
const { container, rerender } = renderSidebar ({ maxWidth: 420 }, 900)
await waitFor (() => {
expect (sidebarWidth (sidebarRoot (container))).toBe ('420px')
})
rerender (sidebarTree ({ maxWidth: 300 }))
await waitFor (() => {
expect (sidebarWidth (sidebarRoot (container))).toBe ('300px')
})
rerender (sidebarTree ({ maxWidth: 500 }))
await waitFor (() => {
expect (sidebarWidth (sidebarRoot (container))).toBe ('300px')
})
})
}) })
+35 -18
ファイルの表示
@@ -69,6 +69,7 @@ const SidebarComponent: FC<Props> = ({
side = 'left', side = 'left',
}) => { }) => {
const rootRef = useRef<HTMLDivElement | null> (null) const rootRef = useRef<HTMLDivElement | null> (null)
const maxWidthRef = useRef (maxWidth)
const getContainerWidth = useCallback (() => { const getContainerWidth = useCallback (() => {
const containerWidth = rootRef.current const containerWidth = rootRef.current
?.closest ('[data-sidebar-container]') ?.closest ('[data-sidebar-container]')
@@ -83,12 +84,14 @@ const SidebarComponent: FC<Props> = ({
typeof window === 'undefined' ? containerWidth : window.innerWidth typeof window === 'undefined' ? containerWidth : window.innerWidth
const viewportMaxWidth = Math.floor (viewportWidth * MAX_VIEWPORT_WIDTH_RATIO) const viewportMaxWidth = Math.floor (viewportWidth * MAX_VIEWPORT_WIDTH_RATIO)
const maxAllowedWidth = const maxAllowedWidth =
maxWidth == null maxWidthRef.current == null
? getSidebarMaxWidth (containerWidth) ? getSidebarMaxWidth (containerWidth)
: Math.max (MIN_SIDEBAR_WIDTH, Math.min (viewportMaxWidth, maxWidth)) : Math.max (
MIN_SIDEBAR_WIDTH,
Math.min (viewportMaxWidth, maxWidthRef.current))
return Math.min (maxAllowedWidth, Math.max (MIN_SIDEBAR_WIDTH, nextWidth)) return Math.min (maxAllowedWidth, Math.max (MIN_SIDEBAR_WIDTH, nextWidth))
}, [maxWidth]) }, [])
const [width, setWidth] = useState (() => ( const [width, setWidth] = useState (() => (
typeof window === 'undefined' typeof window === 'undefined'
@@ -153,6 +156,21 @@ const SidebarComponent: FC<Props> = ({
restoreBodyStyle () restoreBodyStyle ()
}, [clearLongPressTimer, restoreBodyStyle, sidebarKey]) }, [clearLongPressTimer, restoreBodyStyle, sidebarKey])
const applyWidth = useCallback ((nextWidth: number) => {
if (nextWidth === widthRef.current)
return
widthRef.current = nextWidth
setWidth (nextWidth)
}, [])
useEffect (() => {
maxWidthRef.current = maxWidth
const containerWidth = getContainerWidth ()
const nextWidth = getClampedWidth (widthRef.current, containerWidth)
applyWidth (nextWidth)
}, [applyWidth, getClampedWidth, getContainerWidth, maxWidth])
const updateWidth = useCallback ((clientX: number) => { const updateWidth = useCallback ((clientX: number) => {
const state = resizeRef.current const state = resizeRef.current
if (state == null) if (state == null)
@@ -163,9 +181,8 @@ const SidebarComponent: FC<Props> = ({
? clientX - state.startX ? clientX - state.startX
: state.startX - clientX : state.startX - clientX
const nextWidth = getClampedWidth (state.startWidth + delta, containerWidth) const nextWidth = getClampedWidth (state.startWidth + delta, containerWidth)
widthRef.current = nextWidth applyWidth (nextWidth)
setWidth (nextWidth) }, [applyWidth, getClampedWidth, getContainerWidth])
}, [getClampedWidth, getContainerWidth])
const handlePointerDown = (ev: PointerEvent<HTMLDivElement>) => { const handlePointerDown = (ev: PointerEvent<HTMLDivElement>) => {
if (ev.pointerType !== 'mouse' && ev.pointerType !== 'touch') if (ev.pointerType !== 'mouse' && ev.pointerType !== 'touch')
@@ -235,10 +252,9 @@ const SidebarComponent: FC<Props> = ({
const resetWidth = useCallback (() => { const resetWidth = useCallback (() => {
const containerWidth = getContainerWidth () const containerWidth = getContainerWidth ()
const nextWidth = getClampedWidth (DEFAULT_SIDEBAR_WIDTH, containerWidth) const nextWidth = getClampedWidth (DEFAULT_SIDEBAR_WIDTH, containerWidth)
widthRef.current = nextWidth applyWidth (nextWidth)
setWidth (nextWidth)
window.localStorage.removeItem (storageKeyForSidebar (sidebarKey, side)) window.localStorage.removeItem (storageKeyForSidebar (sidebarKey, side))
}, [getClampedWidth, getContainerWidth, sidebarKey, side]) }, [applyWidth, getClampedWidth, getContainerWidth, sidebarKey, side])
const handleDoubleClick = (ev: MouseEvent<HTMLDivElement>) => { const handleDoubleClick = (ev: MouseEvent<HTMLDivElement>) => {
ev.preventDefault () ev.preventDefault ()
@@ -251,9 +267,14 @@ const SidebarComponent: FC<Props> = ({
const nextWidth = getClampedWidth ( const nextWidth = getClampedWidth (
readStoredSidebarWidth (sidebarKey, side), readStoredSidebarWidth (sidebarKey, side),
containerWidth) containerWidth)
widthRef.current = nextWidth applyWidth (nextWidth)
setWidth (nextWidth) }, [applyWidth, getClampedWidth, getContainerWidth, sidebarKey, side])
}, [getClampedWidth, getContainerWidth, sidebarKey, side])
useEffect (() => {
const containerWidth = getContainerWidth ()
const nextWidth = getClampedWidth (widthRef.current, containerWidth)
applyWidth (nextWidth)
}, [applyWidth, getClampedWidth, getContainerWidth])
useEffect (() => { useEffect (() => {
if (typeof window === 'undefined') if (typeof window === 'undefined')
@@ -262,16 +283,12 @@ const SidebarComponent: FC<Props> = ({
const handleResize = () => { const handleResize = () => {
const containerWidth = getContainerWidth () const containerWidth = getContainerWidth ()
const nextWidth = getClampedWidth (widthRef.current, containerWidth) const nextWidth = getClampedWidth (widthRef.current, containerWidth)
if (nextWidth === widthRef.current) applyWidth (nextWidth)
return
widthRef.current = nextWidth
setWidth (nextWidth)
} }
window.addEventListener ('resize', handleResize) window.addEventListener ('resize', handleResize)
return () => window.removeEventListener ('resize', handleResize) return () => window.removeEventListener ('resize', handleResize)
}, [getClampedWidth, getContainerWidth]) }, [applyWidth, getClampedWidth, getContainerWidth])
useEffect (() => { useEffect (() => {
onWidthChange?.(width) onWidthChange?.(width)
+4 -2
ファイルの表示
@@ -6,9 +6,11 @@ import type { FC } from 'react'
const MaterialBasePage: FC = () => ( const MaterialBasePage: FC = () => (
<div className="md:flex md:flex-1 overflow-y-auto md:overflow-y-hidden"> <div
data-sidebar-container
className="md:flex md:flex-1 overflow-y-auto md:overflow-y-hidden">
<MaterialSidebar/> <MaterialSidebar/>
<Outlet/> <Outlet/>
</div>) </div>)
export default MaterialBasePage export default MaterialBasePage
+5 -7
ファイルの表示
@@ -92,16 +92,16 @@ const PostDetailPage: FC<Props> = ({ user }) => {
: 'bg-gray-500 hover:bg-gray-600') : 'bg-gray-500 hover:bg-gray-600')
return ( return (
<div className="md:flex md:flex-1 overflow-y-auto md:overflow-y-hidden"> <div
data-sidebar-container
className="md:flex md:flex-1 overflow-y-auto md:overflow-y-hidden">
<Helmet> <Helmet>
{(post?.thumbnail || post?.thumbnailBase) && ( {(post?.thumbnail || post?.thumbnailBase) && (
<meta name="thumbnail" content={post.thumbnail! || post.thumbnailBase!}/>)} <meta name="thumbnail" content={post.thumbnail! || post.thumbnailBase!}/>)}
{post && <title>{`${ post.title || post.url } | ${ SITE_TITLE }`}</title>} {post && <title>{`${ post.title || post.url } | ${ SITE_TITLE }`}</title>}
</Helmet> </Helmet>
<div className="hidden md:block"> {post && <TagDetailSidebar post={post} className="hidden md:block"/>}
{post && <TagDetailSidebar post={post}/>}
</div>
<MainArea className="relative"> <MainArea className="relative">
{post {post
@@ -179,9 +179,7 @@ const PostDetailPage: FC<Props> = ({ user }) => {
: 'Loading...'} : 'Loading...'}
</MainArea> </MainArea>
<div className="md:hidden"> {post && <TagDetailSidebar post={post} sp className="md:hidden"/>}
{post && <TagDetailSidebar post={post} sp/>}
</div>
</div>) </div>)
} }
+1
ファイルの表示
@@ -70,6 +70,7 @@ const PostListPage: FC = () => {
return ( return (
<div <div
data-sidebar-container
className="md:flex md:flex-1 overflow-y-auto md:overflow-y-hidden" className="md:flex md:flex-1 overflow-y-auto md:overflow-y-hidden"
ref={containerRef}> ref={containerRef}>
<Helmet> <Helmet>