Skip to content

Commit a7c90f6

Browse files
committed
Implement sticky header behavior for settings sections using
1 parent 0a1e13d commit a7c90f6

2 files changed

Lines changed: 69 additions & 18 deletions

File tree

‎packages/ui/src/components/editor/settings/Section/Section.module.scss‎

Lines changed: 19 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -2,10 +2,6 @@
22
display: flex;
33
flex-direction: column;
44
gap: var(--padding-24px);
5-
6-
&--stuck {
7-
box-shadow: var(--vscode-scrollbar-shadow) 0 6px 6px -6px;
8-
}
95
}
106

117
.header {
@@ -18,17 +14,27 @@
1814
padding-top: calc(
1915
var(--padding-60px) + var(--padding-2px)
2016
); // to align with the sidebar title
21-
border-bottom: 2px solid var(--vscode-editorWidget-border);
2217
padding-bottom: var(--padding-10px);
2318
background-color: var(--vscode-editor-background);
24-
}
2519

26-
.title {
27-
margin: 0;
28-
font-size: var(--font-size-20px);
29-
font-weight: 600;
30-
}
20+
&--stuck {
21+
box-shadow: var(--vscode-scrollbar-shadow) 0 6px 6px -6px;
22+
}
23+
24+
&__marker {
25+
position: absolute;
26+
top: 0;
27+
height: 1px;
28+
margin-top: -2px;
29+
}
3130

32-
.subtitle {
33-
font-size: var(--font-size-14px);
31+
&__title {
32+
margin: 0;
33+
font-size: var(--font-size-20px);
34+
font-weight: 600;
35+
}
36+
37+
&__subtitle {
38+
font-size: var(--font-size-14px);
39+
}
3440
}

‎packages/ui/src/components/editor/settings/Section/Section.tsx‎

Lines changed: 50 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
import styles from './Section.module.scss'
2-
import { forwardRef } from 'react'
2+
import { forwardRef, useRef, useState, useEffect } from 'react'
3+
import cn from 'classnames'
34

45
type Props = {
56
title: string
@@ -8,11 +9,55 @@ type Props = {
89
}
910

1011
export const Section = forwardRef<HTMLDivElement, Props>((props, ref) => {
12+
const marker_ref = useRef<HTMLDivElement>(null)
13+
const container_ref = useRef<HTMLDivElement>(null)
14+
const [is_stuck, set_is_stuck] = useState(false)
15+
16+
useEffect(() => {
17+
const marker = marker_ref.current
18+
const container = container_ref.current
19+
if (!marker || !container) return
20+
21+
let is_marker_stuck = false
22+
let is_container_visible = true
23+
24+
const update_stuck_state = () => {
25+
set_is_stuck(is_marker_stuck && is_container_visible)
26+
}
27+
28+
const marker_observer = new IntersectionObserver(
29+
([entry]) => {
30+
is_marker_stuck = !entry.isIntersecting
31+
update_stuck_state()
32+
},
33+
{ threshold: 1 }
34+
)
35+
36+
const container_observer = new IntersectionObserver(
37+
([entry]) => {
38+
is_container_visible = entry.isIntersecting
39+
update_stuck_state()
40+
},
41+
{ threshold: 0 }
42+
)
43+
44+
marker_observer.observe(marker)
45+
container_observer.observe(container)
46+
47+
return () => {
48+
marker_observer.disconnect()
49+
container_observer.disconnect()
50+
}
51+
}, [])
52+
1153
return (
12-
<div ref={ref} className={styles.container}>
13-
<div className={styles.header}>
14-
<div className={styles.title}>{props.title}</div>
15-
<div className={styles.subtitle}>{props.subtitle}</div>
54+
<div ref={container_ref} className={styles.container}>
55+
<div
56+
className={cn(styles.header, { [styles['header--stuck']]: is_stuck })}
57+
>
58+
<div className={styles.header__marker} ref={marker_ref} />
59+
<div className={styles.header__title}>{props.title}</div>
60+
<div className={styles.header__subtitle}>{props.subtitle}</div>
1661
</div>
1762
{props.children}
1863
</div>

0 commit comments

Comments
 (0)