-
Notifications
You must be signed in to change notification settings - Fork 615
fix(frontend): tighten the app shell lines and the playground scrollbars #5461
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Merged
Merged
Changes from all commits
Commits
Show all changes
8 commits
Select commit
Hold shift + click to select a range
91b93af
fix(frontend): tighten the nav rail and make the app shell lines cont…
mmabrouk 2d91a3c
fix(frontend): make the playground panels meet the divider and shrink…
mmabrouk 1012e48
revert(frontend): restore the playground divider and drop the narrow-…
mmabrouk 6bc0a1c
fix(frontend): let the configuration header reach the panel edge
mmabrouk 79e26d8
feat(frontend): overlay scrollbar, model names, narrower playground d…
mmabrouk 6f0d0f0
feat(frontend): render the overlay scrollbar in the config panel
mmabrouk f4f9499
fix(frontend): stop the antd splitter panels from owning a scrollbar
mmabrouk 3bd70de
fix(frontend): end an overlay-scrollbar drag on pointercancel
mmabrouk File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,151 @@ | ||
| "use client" | ||
|
|
||
| import {useCallback, useEffect, useRef, useState} from "react" | ||
|
|
||
| interface OverlayScrollbarProps { | ||
| /** The scroll container this thumb drives. Must be inside a positioned ancestor. */ | ||
| target: HTMLElement | null | ||
| } | ||
|
|
||
| interface Metrics { | ||
| /** Thumb offset from the scroller's top edge, in pixels. */ | ||
| top: number | ||
| height: number | ||
| /** The scroller's own offset inside the positioned ancestor. */ | ||
| trackTop: number | ||
| trackHeight: number | ||
| } | ||
|
|
||
| const MIN_THUMB_HEIGHT = 28 | ||
| const SCROLL_FLASH_MS = 700 | ||
|
|
||
| /** | ||
| * A scrollbar drawn on top of the content instead of beside it. | ||
| * | ||
| * A native scrollbar takes layout width, which shortens every full-width row in the panel by the | ||
| * scrollbar's size. This one floats, so rows still span the panel edge to edge. It shows while the | ||
| * pointer is anywhere in the panel (CSS `group-hover`) or for a moment after a scroll, and it can | ||
| * be dragged. | ||
| * | ||
| * Render it as a sibling of the scroller, inside a `relative group` ancestor. | ||
| */ | ||
| const OverlayScrollbar = ({target}: OverlayScrollbarProps) => { | ||
| const [metrics, setMetrics] = useState<Metrics | null>(null) | ||
| const [scrolling, setScrolling] = useState(false) | ||
| const [dragging, setDragging] = useState(false) | ||
| const flashTimerRef = useRef<ReturnType<typeof setTimeout> | null>(null) | ||
|
|
||
| const measure = useCallback(() => { | ||
| if (!target) { | ||
| setMetrics(null) | ||
| return | ||
| } | ||
| const {scrollHeight, clientHeight, scrollTop, offsetTop} = target | ||
| const scrollable = scrollHeight - clientHeight | ||
| if (scrollable <= 1) { | ||
| setMetrics(null) | ||
| return | ||
| } | ||
| const height = Math.max(MIN_THUMB_HEIGHT, (clientHeight / scrollHeight) * clientHeight) | ||
| const top = (scrollTop / scrollable) * (clientHeight - height) | ||
| setMetrics({top, height, trackTop: offsetTop, trackHeight: clientHeight}) | ||
| }, [target]) | ||
|
|
||
| useEffect(() => { | ||
| if (!target) return | ||
|
|
||
| const onScroll = () => { | ||
| measure() | ||
| setScrolling(true) | ||
| if (flashTimerRef.current) clearTimeout(flashTimerRef.current) | ||
| flashTimerRef.current = setTimeout(() => setScrolling(false), SCROLL_FLASH_MS) | ||
| } | ||
|
|
||
| // The scroller keeps its own size while the content grows, and its children are swapped | ||
| // as the panel loads, so a ResizeObserver on the child would go stale. Remeasure on any | ||
| // subtree change instead, batched to one frame. | ||
| let frame = 0 | ||
| const scheduleMeasure = () => { | ||
| if (frame) return | ||
| frame = requestAnimationFrame(() => { | ||
| frame = 0 | ||
| measure() | ||
| }) | ||
| } | ||
|
|
||
| measure() | ||
| target.addEventListener("scroll", onScroll, {passive: true}) | ||
|
|
||
| const resizeObserver = new ResizeObserver(scheduleMeasure) | ||
| resizeObserver.observe(target) | ||
| const mutationObserver = new MutationObserver(scheduleMeasure) | ||
| mutationObserver.observe(target, {childList: true, subtree: true}) | ||
|
|
||
| return () => { | ||
| target.removeEventListener("scroll", onScroll) | ||
| resizeObserver.disconnect() | ||
| mutationObserver.disconnect() | ||
| if (frame) cancelAnimationFrame(frame) | ||
| if (flashTimerRef.current) clearTimeout(flashTimerRef.current) | ||
| } | ||
| }, [target, measure]) | ||
|
|
||
| const handlePointerDown = useCallback( | ||
| (event: React.PointerEvent<HTMLDivElement>) => { | ||
| if (!target || !metrics) return | ||
| event.preventDefault() | ||
| const startY = event.clientY | ||
| const startScroll = target.scrollTop | ||
| const scrollable = target.scrollHeight - target.clientHeight | ||
| const travel = metrics.trackHeight - metrics.height | ||
| setDragging(true) | ||
|
|
||
| const onMove = (moveEvent: PointerEvent) => { | ||
| if (travel <= 0) return | ||
| const delta = ((moveEvent.clientY - startY) / travel) * scrollable | ||
| target.scrollTop = startScroll + delta | ||
| } | ||
| // pointercancel too: a cancelled stream (touch interruption, app switch) never fires | ||
| // pointerup, which would leave the drag state on and the listeners attached. | ||
| const onUp = () => { | ||
| setDragging(false) | ||
| window.removeEventListener("pointermove", onMove) | ||
| window.removeEventListener("pointerup", onUp) | ||
| window.removeEventListener("pointercancel", onUp) | ||
| } | ||
| window.addEventListener("pointermove", onMove) | ||
| window.addEventListener("pointerup", onUp) | ||
| window.addEventListener("pointercancel", onUp) | ||
| }, | ||
| [target, metrics], | ||
| ) | ||
|
|
||
| if (!metrics) return null | ||
|
|
||
| return ( | ||
| <div | ||
| className="pointer-events-none absolute right-0 z-20 w-2" | ||
| style={{top: metrics.trackTop, height: metrics.trackHeight}} | ||
| > | ||
| <div | ||
| role="presentation" | ||
| onPointerDown={handlePointerDown} | ||
| className={[ | ||
| // touch-none so a touch drag moves the thumb instead of scrolling the page. | ||
| "pointer-events-auto absolute right-0.5 w-1.5 cursor-default touch-none rounded-full", | ||
| "opacity-0 transition-opacity duration-150 group-hover:opacity-100", | ||
| scrolling || dragging ? "!opacity-100" : "", | ||
| ].join(" ")} | ||
| style={{ | ||
| top: metrics.top, | ||
| height: metrics.height, | ||
| background: dragging | ||
| ? "var(--ag-scroll-thumb-hover)" | ||
| : "var(--ag-scroll-thumb)", | ||
| }} | ||
| /> | ||
| </div> | ||
| ) | ||
| } | ||
|
|
||
| export default OverlayScrollbar | ||
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
Uh oh!
There was an error while loading. Please reload this page.