fix: pin the sidebar only when necessary#968
Conversation
|
The latest updates on your projects. Learn more about Vercel for GitHub.
2 Skipped Deployments
|
Codecov Report❌ Patch coverage is
📢 Thoughts on this report? Let us know! |
04640eb to
35b2503
Compare
35b2503 to
ec3d912
Compare
ec3d912 to
22dc507
Compare
22dc507 to
e81158f
Compare
e81158f to
9ee2e91
Compare
9ee2e91 to
b835884
Compare
b835884 to
7fac78e
Compare
7fac78e to
a7f9619
Compare
a7f9619 to
8fb70c1
Compare
📝 WalkthroughWalkthroughAdds a new Vue 3 single-file component at app/components/Package/Sidebar.vue that implements a responsive, scroll-aware sticky sidebar using the Composition API, window size/scroll hooks, template refs and computed styles. Replaces the inline Possibly related PRs
Suggested reviewers
🚥 Pre-merge checks | ✅ 1✅ Passed checks (1 passed)
✏️ Tip: You can configure your own custom pre-merge checks in the settings. ✨ Finishing touches
🧪 Generate unit tests (beta)
No actionable comments were generated in the recent review. 🎉 🧹 Recent nitpick comments
Comment |
There was a problem hiding this comment.
Actionable comments posted: 1
🧹 Nitpick comments (3)
app/components/Package/Sidebar.vue (2)
2-3:windowshadows the globalwindowobject.Naming the
useWindowSize()resultwindowshadows the globalwindowidentifier. While it doesn't cause a functional issue here, it can confuse readers and trip up future contributors who may expectwindowto refer to the browser global.Consider renaming to something like
windowSizeorviewport.♻️ Suggested rename
-const window = useWindowSize() +const windowSize = useWindowSize()Then update line 9:
- return bounds.height.value > window.height.value + return bounds.height.value > windowSize.height.value
17-25: Offset calculation could yield a negative value.When scrolling up (line 24), the offset is:
container.offsetHeight - content.offsetTop - content.offsetHeightIf
content.offsetTop + content.offsetHeight > container.offsetHeightat any point during the scroll (e.g. during layout reflow or when the container hasn't expanded to fit the padded content yet), this could go negative. A negativepaddingBlockEndis treated as invalid by CSS and the declaration will be ignored, but the intent may be wrong.Consider clamping to
Math.max(0, ...):🛡️ Suggested defensive clamp
return direction.value === 'down' - ? content.value.offsetTop - : container.value.offsetHeight - content.value.offsetTop - content.value.offsetHeight + ? Math.max(0, content.value.offsetTop) + : Math.max(0, container.value.offsetHeight - content.value.offsetTop - content.value.offsetHeight)app/pages/package/[[org]]/[name].vue (1)
1344-1376: Remove unused.sidebar-scrollCSS rules.The
.sidebar-scrollclass (lines 1345–1376) is not used anywhere in the template. The sidebar is now implemented via thePackageSidebarcomponent with thearea-sidebarclass. These styles are dead code and should be removed.
8fb70c1 to
bf5956d
Compare
bf5956d to
330aa89
Compare
There was a problem hiding this comment.
Actionable comments posted: 0
Caution
Some comments are outside the diff and can’t be posted inline due to platform limitations.
⚠️ Outside diff range comments (1)
app/pages/package/[[org]]/[name].vue (1)
1344-1376:⚠️ Potential issue | 🟠 MajorRemove unused
.sidebar-scrollCSS rules.The
.sidebar-scrollclass and all associated scrollbar styling rules (lines 1344–1376) are not referenced anywhere in the template. The sidebar is now implemented using the<PackageSidebar>component withclass="area-sidebar", making this CSS block dead code and safe to remove.
🧹 Nitpick comments (2)
app/components/Package/Sidebar.vue (2)
12-15: Verify that thecomputedgetter'spreviousparameter behaves as intended.Vue 3.4+ passes the previous computed value as the first argument to the getter. However, if
activeisfalseand bothscroll.directions.bottomandscroll.directions.topare falsy, the function returnsprevious, which preserves the last direction. This is a neat pattern but subtle — worth a brief inline comment for future maintainers explaining that the default parameter'up'only applies on the very first evaluation.Also note: once the
useWindowScroll→useScrollfix is applied,scroll.directions.bottomandscroll.directions.topwill be booleans, so the ternary chain on line 14 will work correctly.
17-25: Potential negative offset when content is repositioned.The "scrolling up" branch computes:
container.offsetHeight - content.offsetTop - content.offsetHeightIf the content's
offsetTopplus itsoffsetHeightexceeds the container'soffsetHeight(which can happen transiently during layout shifts or when padding is applied by this very component), the result would be negative, producing a negativepaddingBlockEnd. While browsers typically clamp negative padding to 0, it may be worth guarding explicitly withMath.max(…, 0).🛡️ Suggested defensive guard
return direction.value === 'down' ? content.value.offsetTop - : container.value.offsetHeight - content.value.offsetTop - content.value.offsetHeight + : Math.max(0, container.value.offsetHeight - content.value.offsetTop - content.value.offsetHeight)
There was a problem hiding this comment.
Actionable comments posted: 1
🧹 Nitpick comments (3)
app/components/Package/Sidebar.vue (2)
17-25:offsetcan go negative — consider clamping to zero.If the content element's position causes
container.offsetHeight - content.offsetTop - content.offsetHeightto be negative (e.g. during rapid layout shifts or when content shrinks), the computed padding will become a negative pixel value, which browsers ignore but signals unexpected state. AMath.max(0, …)guard would be defensive and cheap.🛡️ Suggested guard
- return direction.value === 'down' - ? content.value.offsetTop - : container.value.offsetHeight - content.value.offsetTop - content.value.offsetHeight + const raw = direction.value === 'down' + ? content.value.offsetTop + : container.value.offsetHeight - content.value.offsetTop - content.value.offsetHeight + + return Math.max(0, raw)
12-15: Narrow the return type and parameter type annotation for type safety.The computed getter correctly uses Vue 3.4+'s previous-value parameter with a default of
'up'. However, the return type: stringis overly broad—the function only ever returns'down'or'up'(or the previous value), and downstream usage inoffsetandstylecomputeds, as well as the template'sdata-directionbinding, all expect one of these two literal values.Suggested change
-const direction = computed((previous = 'up'): string => { +const direction = computed((previous: 'up' | 'down' = 'up'): 'up' | 'down' => {app/pages/package/[[org]]/[name].vue (1)
1344-1376: Remove the unused.sidebar-scrollCSS block.The
.sidebar-scrollclass defined at lines 1344–1376 is not referenced anywhere in the codebase and can be safely removed. This scoped CSS is dead code that's no longer needed.
330aa89 to
98d1c18
Compare
98d1c18 to
19e5ae8
Compare
There was a problem hiding this comment.
Actionable comments posted: 1
🧹 Nitpick comments (4)
app/components/Package/Sidebar.vue (2)
17-25:offsetTop/offsetHeightreads are not reactive — offset may go stale.The
offsetcomputed readscontent.value.offsetTopandcontainer.value.offsetHeightdirectly from the DOM, which are not tracked by Vue's reactivity system. The computed only re-evaluates whenactiveordirectionchanges, so the captured offset reflects the DOM state at the moment of the last direction change. This works for the scroll-pinning use case (offset is "frozen" until the next direction flip), but if the sidebar content resizes dynamically (e.g. lazy-loaded sections, expandable accordions), the offset won't update.Worth considering whether
useElementBounding(which is reactive) could provide the necessary values instead, or whether a manualupdatetrigger on content resize would be prudent. If the sidebar content is effectively static once rendered, this is fine as-is.
12-15: Add an inline comment explaining the Vue 3.4+ computed previous-value pattern.The
computed((previous = 'up') => ...)pattern on line 12 uses the Vue 3.4+ feature where the getter receives the previous computed value as its first argument. The= 'up'default handles the initialundefinedcase. While functionally correct, a brief comment would clarify this non-obvious pattern for future readers:// previous parameter receives the prior direction value (Vue 3.4+) const direction = computed((previous = 'up'): string => { if (!active.value) return previous return scroll.directions.bottom ? 'down' : scroll.directions.top ? 'up' : previous })test/nuxt/components/PackageSidebar.spec.ts (2)
22-61: Test suite lacks coverage for the component's core scroll-direction behaviour.The sidebar's primary feature — switching between
direction="up"anddirection="down"based on scroll, and computing offset/style accordingly — has no test coverage. Consider adding tests for:
data-active="true"whenmockContentHeight > mockViewportHeightdata-directiontoggling to"down"whenmockScrollDirections.bottom = true- Offset/style computation (padding applied in the correct direction)
🧪 Example additional tests
it('sets active=true when content is taller than viewport', async () => { mockContentHeight.value = 1500 const wrapper = await mountSuspended(Sidebar) expect(wrapper.attributes('data-active')).toBe('true') }) it('sets direction to down when scrolling down while active', async () => { mockContentHeight.value = 1500 const wrapper = await mountSuspended(Sidebar) expect(wrapper.attributes('data-direction')).toBe('up') mockScrollDirections.bottom = true await wrapper.vm.$nextTick() expect(wrapper.attributes('data-direction')).toBe('down') })
11-20: Mock foruseElementBoundingomits most return properties.The mock only returns
{ height, update }, butuseElementBoundingreturns many more reactive properties (width,top,left,right,bottom,x,y). This works today because the component only readsheight, but if the component ever accesses another property, this mock will cause a confusing test failure. Consider spreading a minimal set of default refs to future-proof.
19e5ae8 to
4bfb56f
Compare
4bfb56f to
4b5a2b1
Compare
4b5a2b1 to
3468662
Compare
|
@thasmo is attempting to deploy a commit to the serhalp's projects Team on Vercel. A member of the Team first needs to authorize it. |
3468662 to
a50ee52
Compare
a50ee52 to
9b0b10d
Compare
43081j
left a comment
There was a problem hiding this comment.
Unsure if my approach with the test rework is exactly right but we can always tweak it later if it isn't.
Looks good to me 👍
|
Fixed. |
9b0b10d to
a23277f
Compare
This makes the sidebar scrollable with the document, but it pins it when needed.