Skip to content

Commit 93d2c3e

Browse files
committed
feat: Optionally disable scrolling in panel layout
1 parent 8c00352 commit 93d2c3e

6 files changed

Lines changed: 172 additions & 2 deletions

File tree

Lines changed: 106 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,106 @@
1+
// Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
2+
// SPDX-License-Identifier: Apache-2.0
3+
import React, { useContext } from 'react';
4+
5+
import { Button, Checkbox, SegmentedControl, SpaceBetween } from '~components';
6+
import PanelLayout, { PanelLayoutProps } from '~components/panel-layout';
7+
import { colorBackgroundCellShaded } from '~design-tokens';
8+
9+
import AppContext, { AppContextType } from '../app/app-context';
10+
import { SimplePage } from '../app/templates';
11+
12+
type PageContext = React.Context<
13+
AppContextType<{
14+
display: PanelLayoutProps.Display;
15+
panelPosition: PanelLayoutProps.PanelPosition;
16+
disablePanelScrolling: boolean;
17+
disableMainScrolling: boolean;
18+
tallContent: boolean;
19+
}>
20+
>;
21+
22+
// Buttons flush with both inline edges, to check whether their focus rings are clipped.
23+
function EdgeContent({ label, tall }: { label: string; tall: boolean }) {
24+
return (
25+
<div style={{ background: colorBackgroundCellShaded, blockSize: tall ? 600 : '100%' }}>
26+
<div style={{ display: 'flex', justifyContent: 'space-between' }}>
27+
<Button>{label} start</Button>
28+
<Button>{label} end</Button>
29+
</div>
30+
</div>
31+
);
32+
}
33+
34+
export default function PanelLayoutDisableScrollingPage() {
35+
const {
36+
urlParams: {
37+
display = 'all',
38+
panelPosition = 'side-start',
39+
disablePanelScrolling = false,
40+
disableMainScrolling = false,
41+
tallContent = false,
42+
},
43+
setUrlParams,
44+
} = useContext(AppContext as PageContext);
45+
46+
return (
47+
<SimplePage
48+
title="Panel layout with disabled scrolling"
49+
screenshotArea={{}}
50+
settings={
51+
<SpaceBetween size="s">
52+
<SegmentedControl
53+
label="Display"
54+
options={[
55+
{ id: 'all', text: 'all' },
56+
{ id: 'panel-only', text: 'panel-only' },
57+
{ id: 'main-only', text: 'main-only' },
58+
]}
59+
selectedId={display}
60+
onChange={({ detail }) => setUrlParams({ display: detail.selectedId as PanelLayoutProps.Display })}
61+
/>
62+
<SegmentedControl
63+
label="Panel position"
64+
options={[
65+
{ id: 'side-start', text: 'side-start' },
66+
{ id: 'side-end', text: 'side-end' },
67+
]}
68+
selectedId={panelPosition}
69+
onChange={({ detail }) =>
70+
setUrlParams({ panelPosition: detail.selectedId as PanelLayoutProps.PanelPosition })
71+
}
72+
/>
73+
<Checkbox
74+
checked={disablePanelScrolling}
75+
onChange={({ detail }) => setUrlParams({ disablePanelScrolling: detail.checked })}
76+
>
77+
disablePanelScrolling
78+
</Checkbox>
79+
<Checkbox
80+
checked={disableMainScrolling}
81+
onChange={({ detail }) => setUrlParams({ disableMainScrolling: detail.checked })}
82+
>
83+
disableMainScrolling
84+
</Checkbox>
85+
<Checkbox checked={tallContent} onChange={({ detail }) => setUrlParams({ tallContent: detail.checked })}>
86+
Tall content
87+
</Checkbox>
88+
</SpaceBetween>
89+
}
90+
>
91+
<div style={{ blockSize: 300 }}>
92+
<PanelLayout
93+
resizable={true}
94+
defaultPanelSize={300}
95+
minPanelSize={200}
96+
display={display}
97+
panelPosition={panelPosition}
98+
disablePanelScrolling={disablePanelScrolling}
99+
disableMainScrolling={disableMainScrolling}
100+
panelContent={<EdgeContent label="Panel" tall={tallContent} />}
101+
mainContent={<EdgeContent label="Main" tall={tallContent} />}
102+
/>
103+
</div>
104+
</SimplePage>
105+
);
106+
}

‎src/__tests__/snapshot-tests/__snapshots__/documenter.test.ts.snap‎

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -21649,6 +21649,22 @@ The actual size will be constrained by \`minPanelSize\` and \`maxPanelSize\`, if
2164921649
"optional": true,
2165021650
"type": "number",
2165121651
},
21652+
{
21653+
"description": "Disables scrolling of the main content area. Use this when the main content manages its own scrolling,
21654+
or to prevent elements near the edges (such as focus rings) from being clipped.
21655+
Content that doesn't fit is not clipped and overflows the main content area.",
21656+
"name": "disableMainScrolling",
21657+
"optional": true,
21658+
"type": "boolean",
21659+
},
21660+
{
21661+
"description": "Disables scrolling of the panel content area. Use this when the panel content manages its own scrolling,
21662+
or to prevent elements near the edges (such as focus rings) from being clipped.
21663+
Content that doesn't fit is not clipped and overflows the panel area.",
21664+
"name": "disablePanelScrolling",
21665+
"optional": true,
21666+
"type": "boolean",
21667+
},
2165221668
{
2165321669
"defaultValue": "'all'",
2165421670
"description": "Determines which content is displayed:

‎src/panel-layout/index.tsx‎

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,12 @@ const PanelLayout = React.forwardRef<PanelLayoutProps.Ref, PanelLayoutProps>(fun
1616
ref
1717
) {
1818
const baseComponentProps = useBaseComponent('PanelLayout', {
19-
props: { panelPosition, resizable },
19+
props: {
20+
panelPosition,
21+
resizable,
22+
disablePanelScrolling: props.disablePanelScrolling,
23+
disableMainScrolling: props.disableMainScrolling,
24+
},
2025
metadata: {
2126
mainFocusable: props.mainFocusable !== undefined,
2227
panelFocusable: props.panelFocusable !== undefined,

‎src/panel-layout/interfaces.ts‎

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -67,6 +67,20 @@ export interface PanelLayoutProps extends BaseComponentProps {
6767
*/
6868
mainFocusable?: PanelLayoutProps.FocusableConfig;
6969

70+
/**
71+
* Disables scrolling of the panel content area. Use this when the panel content manages its own scrolling,
72+
* or to prevent elements near the edges (such as focus rings) from being clipped.
73+
* Content that doesn't fit is not clipped and overflows the panel area.
74+
*/
75+
disablePanelScrolling?: boolean;
76+
77+
/**
78+
* Disables scrolling of the main content area. Use this when the main content manages its own scrolling,
79+
* or to prevent elements near the edges (such as focus rings) from being clipped.
80+
* Content that doesn't fit is not clipped and overflows the main content area.
81+
*/
82+
disableMainScrolling?: boolean;
83+
7084
/**
7185
* Determines which content is displayed:
7286
* - 'all': Both panel and main content are displayed.

‎src/panel-layout/internal.tsx‎

Lines changed: 10 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -38,6 +38,8 @@ const InternalPanelLayout = React.forwardRef<PanelLayoutProps.Ref, InternalPanel
3838
maxPanelSize,
3939
i18nStrings,
4040
display,
41+
disablePanelScrolling,
42+
disableMainScrolling,
4143
panelFocusable,
4244
mainFocusable,
4345
__internalRootRef,
@@ -141,7 +143,14 @@ const InternalPanelLayout = React.forwardRef<PanelLayoutProps.Ref, InternalPanel
141143
<div
142144
{...baseProps}
143145
ref={mergedRef}
144-
className={clsx(baseProps.className, styles.root, testStyles.root, styles[`display-${display}`])}
146+
className={clsx(
147+
baseProps.className,
148+
styles.root,
149+
testStyles.root,
150+
styles[`display-${display}`],
151+
disablePanelScrolling && styles['disable-panel-scrolling'],
152+
disableMainScrolling && styles['disable-main-scrolling']
153+
)}
145154
>
146155
{panelPosition === 'side-end' && wrappedMainContent}
147156
<div

‎src/panel-layout/styles.scss‎

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -20,11 +20,22 @@
2020
outline-offset: calc(-1 * #{$width});
2121
}
2222

23+
// Non-scrolling areas must not clip (so focus rings can extend past their edges)
24+
// and must be allowed to shrink below their content width.
25+
@mixin non-scrolling {
26+
overflow: visible;
27+
min-inline-size: 0;
28+
}
29+
2330
.root {
2431
@include styles.styles-reset;
2532
block-size: 100%;
2633
overflow: hidden;
2734
display: flex;
35+
&.disable-panel-scrolling,
36+
&.disable-main-scrolling {
37+
overflow: visible;
38+
}
2839
}
2940

3041
.panel {
@@ -50,6 +61,12 @@
5061
overflow-y: auto;
5162
overflow-x: visible;
5263
}
64+
.disable-panel-scrolling > & {
65+
@include non-scrolling;
66+
}
67+
.disable-panel-scrolling > & > .panel-content {
68+
@include non-scrolling;
69+
}
5370
}
5471

5572
.content {
@@ -59,6 +76,9 @@
5976
.display-panel-only > & {
6077
display: none;
6178
}
79+
.disable-main-scrolling > & {
80+
@include non-scrolling;
81+
}
6282
@include focus-visible.when-visible {
6383
@include container-inner-focus-ring;
6484
}

0 commit comments

Comments
 (0)