11
2- import { useMemo , useState } from 'react' ;
2+ import { useMemo , useState , useContext , useRef , useLayoutEffect } from 'react' ;
33import RuleComponent , { SkeletonRule } from './Rule' ;
44import { Degree as DegreeType , DegreePlan , Fulfillment , Rule , Degree as DegreeD } from '@/types' ;
55import styled from '@emotion/styled' ;
@@ -44,6 +44,9 @@ const DegreeHeaderContainer = styled.div`
4444 color: #FFF;
4545 padding: 0.75rem 1.25rem;
4646 border-radius: var(--req-item-radius);
47+ position: sticky;
48+ top: 0;
49+ z-index: 1000;
4750`
4851
4952const ReqPanelTitle = styled . div `
@@ -52,9 +55,7 @@ const ReqPanelTitle = styled.div`
5255`
5356
5457const DegreeBody = styled . div `
55- margin-top: .5rem;
56- overflow-y: auto;
57- overflow-x: hidden;
58+ overflow-y: none;
5859`
5960
6061export const DegreeYear = styled . span `
@@ -84,6 +85,27 @@ const AddButton = styled.div`
8485const ReqPanelBody = styled ( PanelBody ) `
8586 overflow-y: scroll;
8687 padding: 1.5rem;
88+ gap: 0;
89+ `
90+
91+ // pr, mr for scroll bar
92+ const ReqContent = styled . div `
93+ padding: 0;
94+ padding-right: 12px;
95+ margin-right: -12px;
96+ display: flex;
97+ flex-direction: column;
98+ gap: .5rem;
99+ overflow-y: scroll;
100+ `
101+
102+ export const HEADER_DEFAULT_BUFFER = 8 ;
103+ export const WhiteSpace = styled . div < { $headerHeight : number , $zIndex : number } > `
104+ height: ${ HEADER_DEFAULT_BUFFER } px;
105+ background-color: white;
106+ z-index: ${ ( props ) => props . $zIndex || 500 } ;
107+ position: sticky;
108+ top: ${ ( props ) => props . $headerHeight } px;
87109`
88110
89111interface DegreeHeaderProps {
@@ -93,12 +115,21 @@ interface DegreeHeaderProps {
93115 collapsed : boolean ,
94116 editMode : boolean ,
95117 skeleton ?: boolean ,
118+ containerRef ?: React . Ref < HTMLDivElement >
96119}
97120
98- const DegreeHeader = ( { degree, remove, setCollapsed, collapsed, editMode, skeleton } : DegreeHeaderProps ) => {
121+ const DegreeHeader = ( {
122+ degree,
123+ remove,
124+ setCollapsed,
125+ collapsed,
126+ editMode,
127+ skeleton,
128+ containerRef
129+ } : DegreeHeaderProps ) => {
99130 const degreeName = ! skeleton ? `${ degree . degree } in ${ degree . major_name } ${ degree . concentration ? `(${ degree . concentration_name } )` : '' } ` : < DarkBlueBackgroundSkeleton width = "10em" /> ;
100131 return (
101- < DegreeHeaderContainer onClick = { ( ) => setCollapsed ( ! collapsed ) } >
132+ < DegreeHeaderContainer ref = { containerRef } onClick = { ( ) => setCollapsed ( ! collapsed ) } >
102133 < DegreeTitleWrapper >
103134 < div >
104135 { degreeName }
@@ -167,8 +198,37 @@ const computeRuleTree = ({activeDegreePlanId, rule, rulesToFulfillments, rulesTo
167198}
168199
169200
170- const Degree = ( { allRuleLeaves, degree, rulesToFulfillments, rulesToUnselectedFulfillments, activeDegreeplan, editMode, setModalKey, setModalObject, isLoading } : any ) => {
201+ const Degree = ( {
202+ allRuleLeaves,
203+ degree,
204+ rulesToFulfillments,
205+ rulesToUnselectedFulfillments,
206+ activeDegreeplan,
207+ editMode,
208+ setModalKey,
209+ setModalObject,
210+ isLoading
211+ } : any ) => {
171212 const [ collapsed , setCollapsed ] = useState ( false ) ;
213+
214+ const headerRef = useRef < HTMLDivElement > ( null ) ;
215+ const [ headerHeight , setHeaderHeight ] = useState < number > ( 0 ) ;
216+
217+ useLayoutEffect ( ( ) => {
218+ if ( ! headerRef . current ) return ;
219+
220+ const resizeObserver = new ResizeObserver ( ( entries ) => {
221+ for ( const entry of entries ) {
222+ setHeaderHeight ( entry . target . clientHeight ) ;
223+ }
224+ } ) ;
225+ resizeObserver . observe ( headerRef . current ) ;
226+
227+ return ( ) => {
228+ resizeObserver . disconnect ( ) ;
229+ }
230+ } , [ ] ) ;
231+
172232 if ( isLoading ) {
173233 return (
174234 < div >
@@ -206,6 +266,7 @@ const Degree = ({ allRuleLeaves, degree, rulesToFulfillments, rulesToUnselectedF
206266 return (
207267 < div >
208268 < DegreeHeader
269+ containerRef = { headerRef }
209270 degree = { degree }
210271 key = { degree . id }
211272 remove = { ( ) => {
@@ -217,16 +278,22 @@ const Degree = ({ allRuleLeaves, degree, rulesToFulfillments, rulesToUnselectedF
217278 editMode = { editMode }
218279 skeleton = { false }
219280 />
220- { ! collapsed && ! editMode &&
221- < DegreeBody >
222- { degree && degree . rules . map ( ( rule : any ) => {
223- return (
224- < RuleComponent
225- { ...computeRuleTree ( { activeDegreePlanId : activeDegreeplan . id , rule, rulesToFulfillments, rulesToUnselectedFulfillments, degree } ) }
226- />
227- ) }
228- ) }
229- </ DegreeBody > }
281+ < WhiteSpace $headerHeight = { headerHeight } $zIndex = { 999 } />
282+ { ! collapsed && ! editMode &&
283+ < >
284+ < DegreeBody >
285+ { degree && degree . rules . map ( ( rule : any ) => {
286+ return (
287+ < RuleComponent
288+ headerHeight = { headerHeight }
289+ zIndex = { 999 }
290+ { ...computeRuleTree ( { activeDegreePlanId : activeDegreeplan . id , rule, rulesToFulfillments, rulesToUnselectedFulfillments, degree } ) }
291+ />
292+ ) }
293+ ) }
294+ </ DegreeBody >
295+ </ >
296+ }
230297 </ div >
231298 )
232299}
@@ -287,33 +354,36 @@ const ReqPanel = ({ setModalKey, setModalObject, activeDegreeplan, isLoading }:
287354 < >
288355 { activeDegreeplanDetail &&
289356 < ReqPanelBody >
290- { activeDegreeplanDetail . degrees . length == 0 && ! editMode && < EmptyPanel /> }
291- { activeDegreeplanDetail . degrees . map ( degree => (
292- < Degree
293- allRuleLeaves = { allRuleLeaves }
294- degree = { degree }
295- rulesToFulfillments = { rulesToFulfillments }
296- rulesToUnselectedFulfillments = { rulesToUnselectedFulfillments }
297- activeDegreeplan = { activeDegreeplan }
298- editMode = { editMode }
299- setModalKey = { setModalKey }
300- setModalObject = { setModalObject }
301- isLoading = { isLoading }
302- />
303- ) ) }
304- { editMode && < AddButton role = "button" onClick = { ( ) => {
305- setModalObject ( activeDegreeplan ) ;
306- setModalKey ( "degree-add" ) ;
307- } } >
308- < i className = "fa fa-plus" />
309- < div >
310- Add Degree
311- </ div >
312- </ AddButton > }
357+ < ReqContent >
358+ { activeDegreeplanDetail . degrees . length == 0 && ! editMode && < EmptyPanel /> }
359+ { activeDegreeplanDetail . degrees . map ( degree => (
360+ < Degree
361+ key = { degree . id }
362+ allRuleLeaves = { allRuleLeaves }
363+ degree = { degree }
364+ rulesToFulfillments = { rulesToFulfillments }
365+ rulesToUnselectedFulfillments = { rulesToUnselectedFulfillments }
366+ activeDegreeplan = { activeDegreeplan }
367+ editMode = { editMode }
368+ setModalKey = { setModalKey }
369+ setModalObject = { setModalObject }
370+ isLoading = { isLoading }
371+ />
372+ ) ) }
373+ { editMode && < AddButton role = "button" onClick = { ( ) => {
374+ setModalObject ( activeDegreeplan ) ;
375+ setModalKey ( "degree-add" ) ;
376+ } } >
377+ < i className = "fa fa-plus" />
378+ < div >
379+ Add Degree
380+ </ div >
381+ </ AddButton > }
382+ </ ReqContent >
313383 </ ReqPanelBody >
314384 }
315385 </ > }
316386 </ PanelContainer >
317387 ) ;
318388}
319- export default ReqPanel ;
389+ export default ReqPanel ;
0 commit comments