Skip to content
Merged
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion packages/react-core/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -54,7 +54,7 @@
"tslib": "^2.8.1"
},
"devDependencies": {
"@patternfly/patternfly": "6.5.0-prerelease.78",
"@patternfly/patternfly": "6.5.0-prerelease.80",
"case-anything": "^3.1.2",
"css": "^3.0.0",
"fs-extra": "^11.3.3"
Expand Down
10 changes: 10 additions & 0 deletions packages/react-core/src/components/Page/PageGroup.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,10 @@ export interface PageGroupProps extends React.HTMLProps<HTMLDivElement> {
xl?: 'top' | 'bottom';
'2xl'?: 'top' | 'bottom';
};
/** @beta Applies the base sticky positioning to the top or bottom of the scroll parent container. */
stickyBase?: 'top' | 'bottom';
/** @beta Flag indicating if the group has stuck styling, applied when the group is not at the edge of the scroll parent container. */
isStickyStuck?: boolean;
/** Enables the page group to fill the available vertical space if true, or disable filling if false. */
isFilled?: boolean;
/** Modifier indicating if PageGroup should have a shadow at the top */
Expand All @@ -37,6 +41,8 @@ export const PageGroup = ({
className = '',
children,
stickyOnBreakpoint,
stickyBase,
isStickyStuck = false,
isFilled,
hasShadowTop = false,
hasShadowBottom = false,
Expand All @@ -61,6 +67,10 @@ export const PageGroup = ({
className={css(
styles.pageMainGroup,
formatBreakpointMods(stickyOnBreakpoint, styles, 'sticky-', getVerticalBreakpoint(height), true),
stickyBase === 'top' && styles.modifiers.stickyTopBase,
stickyBase === 'bottom' && styles.modifiers.stickyBottomBase,
isStickyStuck && stickyBase === 'top' && styles.modifiers.stickyTopStuck,
isStickyStuck && stickyBase === 'bottom' && styles.modifiers.stickyBottomStuck,
isFilled === false && styles.modifiers.noFill,
isFilled === true && styles.modifiers.fill,
hasShadowTop && styles.modifiers.shadowTop,
Expand Down
10 changes: 10 additions & 0 deletions packages/react-core/src/components/Page/PageSection.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -51,6 +51,10 @@ export interface PageSectionProps extends React.HTMLProps<HTMLDivElement> {
xl?: 'top' | 'bottom';
'2xl'?: 'top' | 'bottom';
};
/** @beta Applies the base sticky positioning to the top or bottom of the scroll parent container. */
stickyBase?: 'top' | 'bottom';
/** @beta Flag indicating if the section has stuck styling, applied when the section is not at the edge of the scroll parent container. */
isStickyStuck?: boolean;
/** Modifier indicating if PageSection should have a shadow at the top */
hasShadowTop?: boolean;
/** Modifier indicating if PageSection should have a shadow at the bottom */
Expand Down Expand Up @@ -96,6 +100,8 @@ export const PageSection: React.FunctionComponent<PageSectionProps> = ({
isWidthLimited = false,
isCenterAligned = false,
stickyOnBreakpoint,
stickyBase,
isStickyStuck = false,
hasShadowTop = false,
hasShadowBottom = false,
hasOverflowScroll = false,
Expand Down Expand Up @@ -124,6 +130,10 @@ export const PageSection: React.FunctionComponent<PageSectionProps> = ({
variantType[type],
formatBreakpointMods(padding, styles),
formatBreakpointMods(stickyOnBreakpoint, styles, 'sticky-', getVerticalBreakpoint(height), true),
stickyBase === 'top' && styles.modifiers.stickyTopBase,
stickyBase === 'bottom' && styles.modifiers.stickyBottomBase,
isStickyStuck && stickyBase === 'top' && styles.modifiers.stickyTopStuck,
isStickyStuck && stickyBase === 'bottom' && styles.modifiers.stickyBottomStuck,
type === PageSectionTypes.default && variantStyle[variant],
isFilled === false && styles.modifiers.noFill,
isFilled === true && styles.modifiers.fill,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -101,3 +101,55 @@ test(`Renders with ${styles.modifiers.noPlainOnGlass} class when isNoPlainOnGlas

expect(screen.getByText('test')).toHaveClass(styles.modifiers.noPlainOnGlass);
});

test(`Does not add sticky base or sticky stuck classes by default`, () => {
render(<PageGroup>test</PageGroup>);
const group = screen.getByText('test');
expect(group).not.toHaveClass(styles.modifiers.stickyTopBase);
expect(group).not.toHaveClass(styles.modifiers.stickyBottomBase);
expect(group).not.toHaveClass(styles.modifiers.stickyTopStuck);
expect(group).not.toHaveClass(styles.modifiers.stickyBottomStuck);
});

test(`Adds ${styles.modifiers.stickyTopBase} without stuck class when stickyBase="top"`, () => {
render(<PageGroup stickyBase="top">test</PageGroup>);
const group = screen.getByText('test');
expect(group).toHaveClass(styles.modifiers.stickyTopBase);
expect(group).not.toHaveClass(styles.modifiers.stickyTopStuck);
});

test(`Adds ${styles.modifiers.stickyBottomBase} without stuck class when stickyBase="bottom"`, () => {
render(<PageGroup stickyBase="bottom">test</PageGroup>);
const group = screen.getByText('test');
expect(group).toHaveClass(styles.modifiers.stickyBottomBase);
expect(group).not.toHaveClass(styles.modifiers.stickyBottomStuck);
});

test(`Adds ${styles.modifiers.stickyTopStuck} when stickyBase="top" and isStickyStuck`, () => {
render(
<PageGroup stickyBase="top" isStickyStuck>
test
</PageGroup>
);
const group = screen.getByText('test');
expect(group).toHaveClass(styles.modifiers.stickyTopBase);
expect(group).toHaveClass(styles.modifiers.stickyTopStuck);
});

test(`Adds ${styles.modifiers.stickyBottomStuck} when stickyBase="bottom" and isStickyStuck`, () => {
render(
<PageGroup stickyBase="bottom" isStickyStuck>
test
</PageGroup>
);
const group = screen.getByText('test');
expect(group).toHaveClass(styles.modifiers.stickyBottomBase);
expect(group).toHaveClass(styles.modifiers.stickyBottomStuck);
});

test(`Does not add stuck class when isStickyStuck is true but stickyBase is not set`, () => {
render(<PageGroup isStickyStuck>test</PageGroup>);
const group = screen.getByText('test');
expect(group).not.toHaveClass(styles.modifiers.stickyTopStuck);
expect(group).not.toHaveClass(styles.modifiers.stickyBottomStuck);
});
Original file line number Diff line number Diff line change
Expand Up @@ -199,3 +199,67 @@ test(`Renders with ${styles.modifiers.noPlainOnGlass} class when isNoPlainOnGlas

expect(screen.getByText('test')).toHaveClass(styles.modifiers.noPlainOnGlass);
});

test(`Does not add sticky base or sticky stuck classes by default`, () => {
render(<PageSection component="main">test</PageSection>);
const section = screen.getByRole('main');
expect(section).not.toHaveClass(styles.modifiers.stickyTopBase);
expect(section).not.toHaveClass(styles.modifiers.stickyBottomBase);
expect(section).not.toHaveClass(styles.modifiers.stickyTopStuck);
expect(section).not.toHaveClass(styles.modifiers.stickyBottomStuck);
});

test(`Adds ${styles.modifiers.stickyTopBase} without stuck class when stickyBase="top"`, () => {
render(
<PageSection component="main" stickyBase="top">
test
</PageSection>
);
const section = screen.getByRole('main');
expect(section).toHaveClass(styles.modifiers.stickyTopBase);
expect(section).not.toHaveClass(styles.modifiers.stickyTopStuck);
});

test(`Adds ${styles.modifiers.stickyBottomBase} without stuck class when stickyBase="bottom"`, () => {
render(
<PageSection component="main" stickyBase="bottom">
test
</PageSection>
);
const section = screen.getByRole('main');
expect(section).toHaveClass(styles.modifiers.stickyBottomBase);
expect(section).not.toHaveClass(styles.modifiers.stickyBottomStuck);
});

test(`Adds ${styles.modifiers.stickyTopStuck} when stickyBase="top" and isStickyStuck`, () => {
render(
<PageSection component="main" stickyBase="top" isStickyStuck>
test
</PageSection>
);
const section = screen.getByRole('main');
expect(section).toHaveClass(styles.modifiers.stickyTopBase);
expect(section).toHaveClass(styles.modifiers.stickyTopStuck);
});

test(`Adds ${styles.modifiers.stickyBottomStuck} when stickyBase="bottom" and isStickyStuck`, () => {
render(
<PageSection component="main" stickyBase="bottom" isStickyStuck>
test
</PageSection>
);
const section = screen.getByRole('main');
expect(section).toHaveClass(styles.modifiers.stickyBottomBase);
expect(section).toHaveClass(styles.modifiers.stickyBottomStuck);
});

test(`Does not add stuck class when isStickyStuck is true but stickyBase is not set`, () => {
render(
<PageSection component="main" isStickyStuck>
test
</PageSection>
);
const section = screen.getByRole('main');
expect(section).not.toHaveClass(styles.modifiers.stickyTopStuck);
expect(section).not.toHaveClass(styles.modifiers.stickyBottomStuck);
});
12 changes: 11 additions & 1 deletion packages/react-core/src/components/Page/examples/Page.md
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ propComponents:
['Page', 'PageSidebar', 'PageSidebarBody', 'PageSection', 'PageGroup', 'PageBreadcrumb', 'PageToggleButton']
---

import { useState } from 'react';
import { useState, useLayoutEffect, useRef } from 'react';
import BarsIcon from '@patternfly/react-icons/dist/js/icons/bars-icon';
import pageSectionWidthLimitMaxWidth from '@patternfly/react-tokens/dist/esm/c_page_section_m_limit_width_MaxWidth';

Expand Down Expand Up @@ -131,3 +131,13 @@ To remove the default background color from a page section or group, use the `is
```ts file="./PagePlainSections.tsx"

```

### Dynamic sticky section

A page section may be made sticky with separate control of its sticky positioning and stuck styling using the `stickyBase` and `isStickyStuck` properties. The `stickyBase` property accepts a value of `"top"` or `"bottom"` and applies the base sticky positioning in the given direction. The `isStickyStuck` property applies visual "stuck" styling such as a background, box shadow, and border, and should be toggled based on the scroll position of the scroll parent container.

In this example, a scroll event listener on the scroll parent container toggles `isStickyStuck` when `scrollTop > 0`, so the stuck styling appears only when the content is scrolled.

```ts file="./PageDynamicStickySection.tsx"

```
Original file line number Diff line number Diff line change
@@ -0,0 +1,108 @@
import { useLayoutEffect, useState, useRef } from 'react';
import {
Page,
Masthead,
MastheadMain,
MastheadBrand,
MastheadLogo,
MastheadContent,
PageSection,
Toolbar,
ToolbarContent,
ToolbarItem,
Breadcrumb,
BreadcrumbItem,
Content
} from '@patternfly/react-core';

const useIsStuckFromScrollParent = ({
shouldTrack,
scrollParentRef
}: {
shouldTrack: boolean;
scrollParentRef: React.RefObject<any>;
}): boolean => {
const [isStuck, setIsStuck] = useState(false);

useLayoutEffect(() => {
if (!shouldTrack) {
setIsStuck(false);
return;
}

const scrollElement = scrollParentRef.current;
if (!scrollElement) {
setIsStuck(false);
return;
}

const syncFromScroll = () => {
setIsStuck(scrollElement.scrollTop > 0);
};
syncFromScroll();
scrollElement.addEventListener('scroll', syncFromScroll, { passive: true });
return () => scrollElement.removeEventListener('scroll', syncFromScroll);
}, [shouldTrack, scrollParentRef]);

return isStuck;
};

export const PageDynamicStickySection: React.FunctionComponent = () => {
const scrollParentRef = useRef<HTMLDivElement>(null);
const isStickyStuck = useIsStuckFromScrollParent({ shouldTrack: true, scrollParentRef });

const headerToolbar = (
<Toolbar id="dynamic-sticky-toolbar">
<ToolbarContent>
<ToolbarItem>header-tools</ToolbarItem>
</ToolbarContent>
</Toolbar>
);

const masthead = (
<Masthead>
<MastheadMain>
<MastheadBrand>
<MastheadLogo href="https://patternfly.org" target="_blank">
Logo
</MastheadLogo>
</MastheadBrand>
</MastheadMain>
<MastheadContent>{headerToolbar}</MastheadContent>
</Masthead>
);

return (
<Page masthead={masthead}>
<div ref={scrollParentRef} style={{ overflowY: 'auto', height: '100%' }}>
<PageSection type="breadcrumb" stickyBase="top" isStickyStuck={isStickyStuck}>
<Breadcrumb>
<BreadcrumbItem>Section home</BreadcrumbItem>
<BreadcrumbItem to="#">Section title</BreadcrumbItem>
<BreadcrumbItem to="#" isActive>
Section landing
</BreadcrumbItem>
</Breadcrumb>
</PageSection>
<PageSection>
<Content>
<h1>Main title</h1>
<p>
Scroll the container to see the breadcrumb section above dynamically apply its stuck styling. The section
uses <code>stickyBase=&quot;top&quot;</code> to remain fixed at the top of the scroll parent, and{' '}
<code>isStickyStuck</code> is toggled via a scroll event listener to apply visual styling when the section
is no longer at the top edge.
</p>
</Content>
</PageSection>
{Array.from({ length: 30 }, (_, i) => (
<PageSection key={i} variant={i % 2 === 0 ? 'default' : 'secondary'}>
<Content>
<p>{`Section ${i + 1} content`}</p>
</Content>
</PageSection>
))}
</div>
</Page>
);
};
2 changes: 1 addition & 1 deletion packages/react-docs/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,7 @@
"test:a11y": "patternfly-a11y --config patternfly-a11y.config"
},
"dependencies": {
"@patternfly/patternfly": "6.5.0-prerelease.78",
"@patternfly/patternfly": "6.5.0-prerelease.80",
"@patternfly/react-charts": "workspace:^",
"@patternfly/react-code-editor": "workspace:^",
"@patternfly/react-core": "workspace:^",
Expand Down
2 changes: 1 addition & 1 deletion packages/react-icons/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -35,7 +35,7 @@
"@fortawesome/free-brands-svg-icons": "^5.15.4",
"@fortawesome/free-regular-svg-icons": "^5.15.4",
"@fortawesome/free-solid-svg-icons": "^5.15.4",
"@patternfly/patternfly": "6.5.0-prerelease.78",
"@patternfly/patternfly": "6.5.0-prerelease.80",
"@rhds/icons": "^2.2.0",
"fs-extra": "^11.3.3",
"tslib": "^2.8.1"
Expand Down
2 changes: 1 addition & 1 deletion packages/react-styles/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@
"clean": "rimraf dist css"
},
"devDependencies": {
"@patternfly/patternfly": "6.5.0-prerelease.78",
"@patternfly/patternfly": "6.5.0-prerelease.80",
"change-case": "^5.4.4",
"fs-extra": "^11.3.3"
},
Expand Down
2 changes: 1 addition & 1 deletion packages/react-tokens/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,7 @@
},
"devDependencies": {
"@adobe/css-tools": "^4.4.4",
"@patternfly/patternfly": "6.5.0-prerelease.78",
"@patternfly/patternfly": "6.5.0-prerelease.80",
"fs-extra": "^11.3.3"
}
}
Loading
Loading