@@ -4,17 +4,107 @@ import { usePathname } from "next/navigation";
44import { pagesList } from "./pagesList" ;
55import { AccountMenu } from "./accountMenu" ;
66import { ThemeToggle } from "./[docs_id]/themeToggle" ;
7- import { useSidebarMdContext } from "./[docs_id]/dynamicMdContext" ;
7+ import {
8+ createContext ,
9+ ReactNode ,
10+ useCallback ,
11+ useContext ,
12+ useEffect ,
13+ useState ,
14+ } from "react" ;
15+ import { DynamicMarkdownSection } from "./[docs_id]/pageContent" ;
16+
17+ export interface ISidebarMdContext {
18+ loadedDocsId : string ;
19+ sidebarMdContent : DynamicMarkdownSection [ ] ;
20+ setSidebarMdContent : (
21+ docsId : string ,
22+ content :
23+ | DynamicMarkdownSection [ ]
24+ | ( ( prev : DynamicMarkdownSection [ ] ) => DynamicMarkdownSection [ ] )
25+ ) => void ;
26+ }
27+
28+ const SidebarMdContext = createContext < ISidebarMdContext | null > ( null ) ;
29+
30+ export function useSidebarMdContext ( ) {
31+ const context = useContext ( SidebarMdContext ) ;
32+ if ( ! context ) {
33+ throw new Error (
34+ "useSidebarMdContext must be used within a SidebarMdProvider"
35+ ) ;
36+ }
37+ return context ;
38+ }
39+
40+ export function useSidebarMdContextOptional ( ) {
41+ return useContext ( SidebarMdContext ) ;
42+ }
43+
44+ export function SidebarMdProvider ( { children } : { children : ReactNode } ) {
45+ const [ sidebarMdContent , setSidebarMdContent_ ] = useState <
46+ DynamicMarkdownSection [ ]
47+ > ( [ ] ) ;
48+ const [ loadedDocsId , setLoadedDocsId ] = useState < string > ( "" ) ;
49+ const setSidebarMdContent = useCallback (
50+ (
51+ docsId : string ,
52+ content :
53+ | DynamicMarkdownSection [ ]
54+ | ( ( prev : DynamicMarkdownSection [ ] ) => DynamicMarkdownSection [ ] )
55+ ) => {
56+ setLoadedDocsId ( docsId ) ;
57+ setSidebarMdContent_ ( content ) ;
58+ } ,
59+ [ ]
60+ ) ;
61+ return (
62+ < SidebarMdContext . Provider
63+ value = { {
64+ loadedDocsId,
65+ sidebarMdContent,
66+ setSidebarMdContent,
67+ } }
68+ >
69+ { children }
70+ </ SidebarMdContext . Provider >
71+ ) ;
72+ }
873
974export function Sidebar ( ) {
1075 const pathname = usePathname ( ) ;
11- const docs_id = pathname . replace ( / ^ \/ / , "" ) ;
12- const { sidebarMdContent } = useSidebarMdContext ( ) ;
13-
76+ const currentDocsId = pathname . replace ( / ^ \/ / , "" ) ; // ちょっと遅延がある
77+ const sidebarContext = useSidebarMdContext ( ) ;
78+ // sidebarMdContextの情報が古かったら使わない
79+ const sidebarMdContent =
80+ sidebarContext . loadedDocsId === currentDocsId
81+ ? sidebarContext . sidebarMdContent
82+ : [ ] ;
83+
1484 // 現在表示中のセクション(最初にinViewがtrueのもの)を見つける
1585 const currentSectionIndex = sidebarMdContent . findIndex (
1686 ( section ) => section . inView
1787 ) ;
88+
89+ // 目次の開閉状態
90+ const [ detailsOpen , setDetailsOpen ] = useState < boolean [ ] > ( [ ] ) ;
91+ const currentGroupIndex = pagesList . findIndex ( ( group ) =>
92+ currentDocsId . startsWith ( `${ group . id } -` )
93+ ) ;
94+ useEffect ( ( ) => {
95+ // 表示しているグループが変わったときに現在のグループのdetailsを開く
96+ if ( currentGroupIndex !== - 1 ) {
97+ setDetailsOpen ( ( detailsOpen ) => {
98+ const newDetailsOpen = [ ...detailsOpen ] ;
99+ while ( newDetailsOpen . length <= currentGroupIndex ) {
100+ newDetailsOpen . push ( false ) ;
101+ }
102+ newDetailsOpen [ currentGroupIndex ] = true ;
103+ return newDetailsOpen ;
104+ } ) ;
105+ }
106+ } , [ currentGroupIndex ] ) ;
107+
18108 return (
19109 < div className = "bg-base-200 h-full w-80 overflow-y-auto" >
20110 { /* todo: 背景色ほんとにこれでいい? */ }
@@ -51,9 +141,19 @@ export function Sidebar() {
51141 </ span >
52142
53143 < ul className = "menu w-full" >
54- { pagesList . map ( ( group ) => (
144+ { pagesList . map ( ( group , i ) => (
55145 < li key = { group . id } >
56- < details open = { docs_id . startsWith ( `${ group . id } -` ) } >
146+ < details
147+ open = { ! ! detailsOpen . at ( i ) }
148+ onToggle = { ( e ) => {
149+ const newDetailsOpen = [ ...detailsOpen ] ;
150+ while ( newDetailsOpen . length <= i ) {
151+ newDetailsOpen . push ( false ) ;
152+ }
153+ newDetailsOpen [ i ] = e . currentTarget . open ;
154+ setDetailsOpen ( newDetailsOpen ) ;
155+ } }
156+ >
57157 < summary > { group . lang } </ summary >
58158 < ul >
59159 { group . pages . map ( ( page ) => (
@@ -62,27 +162,31 @@ export function Sidebar() {
62162 < span className = "mr-0" > { page . id } .</ span >
63163 { page . title }
64164 </ Link >
65- { `${ group . id } -${ page . id } ` === docs_id && sidebarMdContent . length > 0 && (
66- < ul className = "ml-4 text-sm" >
67- { sidebarMdContent . slice ( 1 ) . map ( ( section , idx ) => {
68- // idx + 1 は実際のsectionIndexに対応(slice(1)で最初を除外しているため)
69- const isCurrentSection = idx + 1 === currentSectionIndex ;
70- return (
71- < li
72- key = { idx }
73- style = { { marginLeft : section . level - 2 + "em" } }
74- >
75- < Link
76- href = { `#${ idx + 1 } ` }
77- className = { isCurrentSection ? "font-bold" : "" }
165+ { `${ group . id } -${ page . id } ` === currentDocsId &&
166+ sidebarMdContent . length > 0 && (
167+ < ul className = "ml-4 text-sm" >
168+ { sidebarMdContent . slice ( 1 ) . map ( ( section , idx ) => {
169+ // idx + 1 は実際のsectionIndexに対応(slice(1)で最初を除外しているため)
170+ const isCurrentSection =
171+ idx + 1 === currentSectionIndex ;
172+ return (
173+ < li
174+ key = { idx }
175+ style = { { marginLeft : section . level - 2 + "em" } }
78176 >
79- { section . title }
80- </ Link >
81- </ li >
82- ) ;
83- } ) }
84- </ ul >
85- ) }
177+ < Link
178+ href = { `#${ idx + 1 } ` }
179+ className = {
180+ isCurrentSection ? "font-bold" : ""
181+ }
182+ >
183+ { section . title }
184+ </ Link >
185+ </ li >
186+ ) ;
187+ } ) }
188+ </ ul >
189+ ) }
86190 </ li >
87191 ) ) }
88192 </ ul >
0 commit comments