Skip to content

Conversation

@gethinwebster
Copy link
Member

@gethinwebster gethinwebster commented Dec 16, 2025

Description

Create new "panel layout" component

Related links, issue #, if available: AWSUI-61325

How has this been tested?

Added unit and integ tests, will add screenshot tests separately

Review checklist

The following items are to be evaluated by the author(s) and the reviewer(s).

Correctness

  • Changes include appropriate documentation updates.
  • Changes are backward-compatible if not indicated, see CONTRIBUTING.md.
  • Changes do not include unsupported browser features, see CONTRIBUTING.md.
  • Changes were manually tested for accessibility, see accessibility guidelines.

Security

Testing

  • Changes are covered with new/existing unit tests?
  • Changes are covered with new/existing integration tests?

By submitting this pull request, I confirm that you can use, modify, copy, and redistribute this contribution, under the terms of your choice.

@gethinwebster gethinwebster force-pushed the dev-v3-gethinw-panel-layout branch from 14dfc75 to 5b0044a Compare December 16, 2025 15:34
@codecov
Copy link

codecov bot commented Dec 16, 2025

Codecov Report

✅ All modified and coverable lines are covered by tests.
✅ Project coverage is 97.14%. Comparing base (1bfc081) to head (b4c17cd).
⚠️ Report is 5 commits behind head on main.

Additional details and impacted files
@@            Coverage Diff             @@
##             main    #4114      +/-   ##
==========================================
+ Coverage   97.12%   97.14%   +0.01%     
==========================================
  Files         865      869       +4     
  Lines       25362    25464     +102     
  Branches     9155     9209      +54     
==========================================
+ Hits        24634    24736     +102     
  Misses        681      681              
  Partials       47       47              

☔ View full report in Codecov by Sentry.
📢 Have feedback on the report? Share it here.

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.
  • 📦 JS Bundle Analysis: Save yourself from yourself by tracking and limiting bundle sizes in JS merges.

} = useContext(AppContext as PageContext);

return (
<I18nProvider messages={[messages]} locale="en">
Copy link
Member

Choose a reason for hiding this comment

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

nit: There is now a helper component SimplePage for rendering test pages w/o app layout. It includes i18n and screenshot area:

<SimplePage title="Nested Panel Layout Demo" i18n={{}} screenshotArea={{}}>
  <>...</>
</SimplePage>

Copy link
Member

Choose a reason for hiding this comment

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

oh, and it has a slot for settings

<Box variant="h2" padding={{ bottom: 'm' }}>
Artifact
</Box>
<Button
Copy link
Member

Choose a reason for hiding this comment

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

nit: There is no margins between these buttons.

</Box>
);
return (
<div ref={ref} style={{ width: '100%', overflow: 'hidden' }}>
Copy link
Member

Choose a reason for hiding this comment

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

nit: I don't mind inline styles, but since we already use class names e.g. for the ai-artifact-panel, it would be nice to use it here, too - for consistency

Copy link
Member Author

Choose a reason for hiding this comment

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

styles have been removed in latest version

} = useContext(AppContext as PageContext);

return (
<I18nProvider messages={[messages]} locale="en">
Copy link
Member

Choose a reason for hiding this comment

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

oh, and it has a slot for settings

}: PanelLayoutContentProps) => {
const [size, setSize] = useState(Math.max(200, minPanelSize));

const [_actualMaxPanelSize, ref] = useContainerQuery(
Copy link
Member

Choose a reason for hiding this comment

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

The onPanelResize.detail does include totalSize, but it looks like we cannot use it as the callback only fires on user-issued resize. Would it make senes to maybe add another callback that fires when the panel size changes?

We already use the container query inside the component, and if we expose the callback - the consumers won't have to use another container query outside.

Copy link
Member Author

Choose a reason for hiding this comment

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

Nice idea, it also gets rid of a lot of places where teams have to add styled containers (because they no longer need a parent element to attach a ref to). Adding as onLayoutChange

);

return (
<div style={{ height: '100%', overflow: 'hidden' }}>
Copy link
Member

Choose a reason for hiding this comment

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

I noticed that we add these styles to all test pages. Would it make sense to support that on the component itself with props? E.g. some stretchWidth and stretchHeight flags?

Copy link
Member Author

Choose a reason for hiding this comment

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

It's not actually necessary on this dev page, will remove it. Whether (and what exactly) this styling is necessary is very dependent on the specific use-case, so I don't see a strong case for adding this as I think it would end up adding a fair amount of complexity to the API to handle multiple different cases.

Copy link
Member

Choose a reason for hiding this comment

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

Ok, it's fair. Let's then remove it from test pages where it is not needed

onPanelResize={({ detail }) => setSize(detail.panelSize)}
display={display}
panelPosition={panelPosition}
mainFocusable={longMainContent && !buttons ? { ariaLabel: 'Main content' } : undefined}
Copy link
Member

Choose a reason for hiding this comment

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

nit: logically it probably makes more sense to only depend on !buttons, as unless the content is measured or expected to be very small - there is a chance that it will not fit the screen when the viewport is small.

</SpaceBetween>
</Container>
}
mainFocusable={{ ariaLabel: 'Level 2 main' }}
Copy link
Member

Choose a reason for hiding this comment

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

There are two issues with the focus outline: in the app layout page, the focus outline is partially hidden behind the scrollbars. In the nested page, the focus outline is barely visible as of being overlayed by container styles.

Image

Copy link
Member

Choose a reason for hiding this comment

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

I also wonder, if it is possible to implement the panel in such a way that the header is sticky (or effectively sticky), so that only the content part is scrollable. It not or if hard - it might make sense to offer separate slots for header and content, potentially.

Copy link
Member Author

Choose a reason for hiding this comment

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

Pushed an update that seems to work better to me: it's no longer obscured by the scrollbars. I still don't have a solution for the scenario on the nested page (other than adding padding), but I also think in that scenario (as mentioned in a thread above) the ideal solution would be scrolling inside the container which currently would need to be implemented inside the panel contents (i.e. by the consumer), so I think this is a reasonable solution to the most common use-cases

Copy link
Member

Choose a reason for hiding this comment

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

I agree, when a container-like content is added - having the scroll outside does not look right. Is it straightforward to implement an internal scroll, though? There might be challenges are the panels already have overflow styles, added unconditionally.


export interface PanelResizeDetail {
totalSize: number;
panelSize: number;
Copy link
Member

Choose a reason for hiding this comment

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

Can main content size be obtained as totalSize - panelSize, or one must account for the resize handle when resizable=true?

Copy link
Member Author

Choose a reason for hiding this comment

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

panelSize includes the resize handle, so actual available panel content size is smaller if resizable

Copy link
Member

Choose a reason for hiding this comment

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

Should we compensate for that?

E.g. if I have some content which min size is precisely 400px - I should want to know that it can fit into the panel. Unless we do this - the consumer will have to account for the resize handle size, which would require to check for resizable and display props also to figure out if the resize handle size should be subtracted from the panel size.

Copy link
Member

Choose a reason for hiding this comment

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

As we know when the resize handle is shown and its size, we can do the compensation, and then expose totalSize, panelSize, and mainSize in the event handler details (in which case the sum of panel and main size will only equal total size if there is no resize handle).

Copy link
Member Author

Choose a reason for hiding this comment

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

I think that makes it actually harder for teams. For example, the main use-case for that callback is adjusting the maxPanelSize to have an effective minContentSize. But for that to work properly the values have to add up consistently, and if we're compensating for the handle I can't see a way that we can do that that will be easy to understand.

Copy link
Member

Choose a reason for hiding this comment

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

Can we expose an additional panelContentSize (that is panel size minus handle size)?

Copy link
Member

Choose a reason for hiding this comment

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

That can actually be an incremental change, unlike the one I suggested earlier - so we can do it later as well.

@gethinwebster gethinwebster added this pull request to the merge queue Dec 18, 2025
@github-merge-queue github-merge-queue bot removed this pull request from the merge queue due to failed status checks Dec 18, 2025
@gethinwebster gethinwebster added this pull request to the merge queue Dec 18, 2025
@github-merge-queue github-merge-queue bot removed this pull request from the merge queue due to failed status checks Dec 18, 2025
@gethinwebster gethinwebster added this pull request to the merge queue Dec 18, 2025
Merged via the queue into main with commit 1369fb7 Dec 18, 2025
50 checks passed
@gethinwebster gethinwebster deleted the dev-v3-gethinw-panel-layout branch December 18, 2025 14:58
georgylobko pushed a commit that referenced this pull request Dec 30, 2025
ngynmt pushed a commit to ngynmt/components that referenced this pull request Jan 5, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants