Skip to content

Conversation

@reidbarber
Copy link
Member

@reidbarber reidbarber commented Nov 6, 2025

  • Add Adobe copyright footer
  • Make the SideNav / ToC widths static
  • Fix page min height on mobile
  • Fix search menu closing when opening links in new tab (listen for custom navigation event)
  • Show error toast if client route and error page 404 (lost connection or server down)
  • Show error toast if clipboard copies fail
  • Move the markdown copy button into area below table of contents
  • Add "Open in ChatGPT" and "Open in Claude" links to markdown menu
  • Add skeleton page loading (+ pre-filled page title, + optimistically render sidebar and ToC )
  • Added prefetching via pointerover listener

✅ Pull Request Checklist:

  • Included link to corresponding React Spectrum GitHub Issue.
  • Added/updated unit tests and storybook for this change (for new code or code which already has tests).
  • Filled out test instructions.
  • Updated documentation (if it already exists for this component).
  • Looked at the Accessibility Practices for this feature - Aria Practices

📝 Test Instructions:

🧢 Your Project:

@rspbot
Copy link

rspbot commented Nov 6, 2025

@majornista
Copy link
Collaborator

majornista commented Nov 6, 2025

I think we need to add scroll-padding-block-start: 60px; scroll-padding-block-end: 6px; in the SearchMenu where we have a TagGroup that uses position: sticky that sits on top of the ComponentCardView. We may need to do this with break points, because when the page is resized or scaled, the TagGroup can wrap, which means the scroll-padding-block-start will need to be larger.

<div className={style({height: 'full', overflow: 'auto', paddingX: 16, paddingBottom: 16})}>

Copy link
Member

@snowystinger snowystinger left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It's strange, there's still a delay moving to the new page with the skeleton. Then the skeleton appears and disappears so fast that it doesn't feel like it's helping with the transition.

@devongovett
Copy link
Member

@majornista fyi I removed the sticky positioning in this PR: #9135

Copy link
Member

@devongovett devongovett left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

nice! would be cool if we could get optimistic updates working for the sidenav, so when you click a link the selection moves instantly as soon as the skeleton appears. might be able to do that with useOptimistic. ideally the skeleton would also not appear at all if the page load is fast.

</ScrollableToc>
<div className={style({flexShrink: 0})}>
<Divider size="S" styles={style({marginY: 12})} />
<MarkdownMenu url={currentPage.url} />
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Question for the team: do we prefer this at the bottom of the page or at the top (i.e. above "on this page")? Most sites seem to have it at the top. I don't mind the bottom but it's a little less prominent. Also moves around a bit depending on the length of the toc.

Copy link
Member Author

@reidbarber reidbarber Nov 7, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

My reasoning was that the table of contents is more useful most of the time, so it's better when scanning to read that first. Also if we end up adding more actions (feedback button, etc.) it might crowd the top. Definitely open to changing this though.

Also for pages with many sections it renders at the bottom and the ToC has scrolling.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

IMO I like it at the bottom, but it really depends on how prominent we want to make it

@rspbot
Copy link

rspbot commented Nov 7, 2025

@reidbarber reidbarber changed the title docs: S2 docs layout + general improvements WIP: docs: S2 docs layout + general improvements Nov 7, 2025
@rspbot
Copy link

rspbot commented Nov 7, 2025

@rspbot
Copy link

rspbot commented Nov 7, 2025

@reidbarber reidbarber changed the title WIP: docs: S2 docs layout + general improvements docs: S2 docs layout + general improvements Nov 7, 2025
@rspbot
Copy link

rspbot commented Nov 7, 2025

@reidbarber reidbarber changed the title docs: S2 docs layout + general improvements WIP: docs: S2 docs layout + general improvements Nov 7, 2025
@rspbot
Copy link

rspbot commented Nov 10, 2025

@rspbot
Copy link

rspbot commented Nov 10, 2025

@reidbarber reidbarber changed the title WIP: docs: S2 docs layout + general improvements docs: S2 docs layout + general improvements Nov 10, 2025
LFDanLu
LFDanLu previously approved these changes Nov 10, 2025
Copy link
Member

@LFDanLu LFDanLu left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Looks good to me, approving for testing. Just one small comment

// Reset pending state when navigation completes
useEffect(() => {
setPendingPage(null);
}, [currentPage.url]);
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Did you try useOptimistic for this? With that you setState and it will automatically reset whenever the props change.

}

const [basePathname] = pathname.split('?');
const [cleanPathname] = basePathname.split('#');
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If you parsed it with new URL then this shouldn't happen. If it didn't start with http then you could assume it's a relative url to the current location? new URL(urlOrPathname, location.href)

if (raceResult === 'error') {
clearPendingPage();
if (loadingShown) {
setNavigationLoading(false);
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Wonder if we could use the promise returned by fetchRSC as the promise that we use() in the suspense boundary instead of managing a separate state here?

let rscPath = basePath.replace('.html', '.rsc');

// Use prefetched result if available, otherwise fetch
const prefetchedPromise = getPrefetchedPromise(rscPath);
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

One question is how long the prefetched promises are kept around? If someone loads a docs page and then comes back to the tab a week later and clicks a link, will it use a previously cached result or re-fetch? We may be able to rely on the browser cache to handle this as well – if we just call fetchRSC again it should return a cached result if it's still valid.

let res = await fetchRSC<ReactElement>(pathname.replace('.html', '.rsc'));
raceResult = await Promise.race([
fetchPromise.then(() => 'fast' as const).catch(() => 'error' as const),
delayPromise.then(() => 'slow' as const)
Copy link
Member

@devongovett devongovett Nov 10, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We may not need the delay - we kinda want the skeleton to show up as fast as possible so the user knows something is happening, but just not when the result is already cached. We can experiment with it.

@rspbot
Copy link

rspbot commented Nov 10, 2025

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

Projects

None yet

Development

Successfully merging this pull request may close these issues.

7 participants