Skip to content

Commit bb710b7

Browse files
committed
added sticky section
1 parent 0453350 commit bb710b7

File tree

3 files changed

+66
-7
lines changed

3 files changed

+66
-7
lines changed

src/controls/section.tsx

Lines changed: 62 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
import { makeStyles, mergeClasses, Portal, tokens } from '@fluentui/react-components';
2-
import { isFunction, isNotEmptyArray, isNotEmptyString } from '@kwiz/common';
3-
import React from 'react';
2+
import { getScrollParent, isFunction, isNotEmptyArray, isNotEmptyString, isNullOrUndefined } from '@kwiz/common';
3+
import React, { useEffect, useState } from 'react';
4+
import { useElementSize, useRefWithState } from '../helpers';
45
import { useKWIZFluentContext } from '../helpers/context-internal';
56
import { KnownClassNames, mixins, useCommonStyles } from '../styles/styles';
67

@@ -17,6 +18,14 @@ const useStyles = makeStyles({
1718
float: "right",
1819
marginLeft: tokens.spacingHorizontalXXL
1920
},
21+
sticky: {
22+
...mixins.box,
23+
position: "sticky",
24+
top: 0,
25+
overflow: "auto",
26+
height: "fit-content",
27+
maxHeight: "fit-content"
28+
},
2029
selfCentered: {
2130
alignSelf: "center"
2231
}
@@ -32,6 +41,8 @@ export interface ISectionProps {
3241
title?: string;
3342
left?: boolean;
3443
right?: boolean;
44+
/** set height to match scroll parent's height, and stick to top */
45+
sticky?: boolean;
3546
/** true - will add css position fixed. portal will also wrap it in a portal. */
3647
fullscreen?: boolean | "portal";
3748
centerSelf?: boolean;
@@ -55,11 +66,59 @@ export const Section = React.forwardRef<HTMLDivElement, React.PropsWithChildren<
5566
css.push(cssNames.right);
5667
css.push(KnownClassNames.right);
5768
}
69+
else if (props.sticky) {
70+
css.push(cssNames.sticky);
71+
}
72+
73+
/** need scrollparent if we are sticky */
74+
const [scrollParent, setScrollParent] = useState<HTMLElement>(null);
75+
const divRef = useRefWithState<HTMLDivElement>();
76+
77+
//wait for my content to finish loading, it might change scrollparent
78+
const mySize = useElementSize(divRef.ref.current);
79+
80+
useEffect(() => {
81+
//setting the forwardRef
82+
if (!isNullOrUndefined(ref)) {
83+
if (isFunction(ref)) ref(divRef.ref.current);
84+
else (ref as React.MutableRefObject<HTMLDivElement>).current = divRef.ref.current;
85+
}
86+
}, [divRef.value]);
87+
88+
useEffect(() => {
89+
if (props.sticky) {
90+
let scrollParent = getScrollParent(divRef.ref.current ? divRef.ref.current.parentElement : null);
91+
setScrollParent(scrollParent);
92+
}
93+
}, [divRef.value, mySize.height]);
94+
95+
const parentSize = useElementSize(scrollParent);
96+
useEffect(() => {
97+
if (props.sticky && divRef.ref.current) {
98+
let maxHeight = "fit-content";
99+
if (scrollParent) {
100+
let height = parseFloat(getComputedStyle(scrollParent).height);
101+
let myStyle = getComputedStyle(divRef.ref.current);
102+
103+
let pTop = parseFloat(myStyle.paddingTop);
104+
let pBottom = parseFloat(myStyle.paddingBottom);
105+
let mTop = parseFloat(myStyle.marginTop);
106+
let mBottom = parseFloat(myStyle.marginBottom);
107+
if (pTop > 0) height -= pTop;
108+
if (pBottom > 0) height -= pBottom;
109+
if (mTop > 0) height -= mTop;
110+
if (mBottom > 0) height -= mBottom;
111+
112+
maxHeight = `${height}px`;
113+
}
114+
divRef.ref.current.style.maxHeight = maxHeight;
115+
}
116+
}, [props.sticky, parentSize.height, divRef.value]);
58117

59118
//a css class might have space and multiuple classes in it
60119
if (isNotEmptyArray(props.css)) props.css.filter(c => isNotEmptyString(c)).forEach(c => css.push(...c.split(" ")));
61120
if (props.fullscreen) css.push(commonStyles.fullscreen);
62-
const control = <div ref={ref} {...(props.rootProps || {})} title={props.title} style={props.style}
121+
const control = <div ref={divRef.set} {...(props.rootProps || {})} title={props.title} style={props.style}
63122
className={mergeClasses(...css)}
64123
onClick={props.onClick}>
65124
{props.children}

src/helpers/hooks.tsx

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -132,11 +132,11 @@ export function useRefWithState<T>(initialValue?: T, stateOptions: stateExOption
132132
setState(newValue);
133133
}, useEffectOnlyOnMount);
134134
return {
135-
/** pure ref object */
135+
/** ref object for getting latest value in handlers */
136136
ref: asRef,
137-
/** use the value in useEffect dependency */
137+
/** for useEffect dependency */
138138
value: asState,
139-
/** set it by ref={e.set} */
139+
/** for setting on element: ref={e.set} */
140140
set: setRef
141141
};
142142
}

src/styles/styles.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,7 @@ export module mixins {
1111
cursor: "pointer"
1212
}
1313
}
14-
const box: GriffelStyle = {
14+
export const box: GriffelStyle = {
1515
padding: tokens.spacingHorizontalM,
1616
borderRadius: tokens.borderRadiusMedium,
1717
boxShadow: tokens.shadow4,

0 commit comments

Comments
 (0)