From 140d089c993624147610b8c92b9bf0dd270e8be4 Mon Sep 17 00:00:00 2001 From: Gustavo Murcia Date: Wed, 8 Jul 2026 16:00:58 -0400 Subject: [PATCH 1/9] feat(Tearsheet): Initial scaffolding from sample impl in core patternfly --- README.md | 2 +- packages/module/src/Tearsheet/Tearsheet.tsx | 72 +++++++++++++++ packages/module/src/Tearsheet/index.ts | 2 + packages/module/src/Tearsheet/tearsheet.css | 87 +++++++++++++++++++ .../src/TearsheetBody/TearsheetBody.tsx | 15 ++++ packages/module/src/TearsheetBody/index.ts | 2 + .../src/TearsheetFooter/TearsheetFooter.tsx | 16 ++++ packages/module/src/TearsheetFooter/index.ts | 2 + .../src/TearsheetGroup/TearsheetGroup.tsx | 82 +++++++++++++++++ packages/module/src/TearsheetGroup/index.ts | 2 + .../src/TearsheetHeader/TearsheetHeader.tsx | 16 ++++ packages/module/src/TearsheetHeader/index.ts | 2 + 12 files changed, 299 insertions(+), 1 deletion(-) create mode 100644 packages/module/src/Tearsheet/Tearsheet.tsx create mode 100644 packages/module/src/Tearsheet/index.ts create mode 100644 packages/module/src/Tearsheet/tearsheet.css create mode 100644 packages/module/src/TearsheetBody/TearsheetBody.tsx create mode 100644 packages/module/src/TearsheetBody/index.ts create mode 100644 packages/module/src/TearsheetFooter/TearsheetFooter.tsx create mode 100644 packages/module/src/TearsheetFooter/index.ts create mode 100644 packages/module/src/TearsheetGroup/TearsheetGroup.tsx create mode 100644 packages/module/src/TearsheetGroup/index.ts create mode 100644 packages/module/src/TearsheetHeader/TearsheetHeader.tsx create mode 100644 packages/module/src/TearsheetHeader/index.ts diff --git a/README.md b/README.md index b76a7e5b..b6507124 100644 --- a/README.md +++ b/README.md @@ -29,7 +29,7 @@ Please reference [PatternFly's AI-assisted development guidelines](https://githu ### Before adding a new component: - make sure your use case is new/complex enough to be added to this extension -- the component should bring a value value above and beyond existing PatternFly components +- the component should bring a value above and beyond existing PatternFly components ### To add a new component: diff --git a/packages/module/src/Tearsheet/Tearsheet.tsx b/packages/module/src/Tearsheet/Tearsheet.tsx new file mode 100644 index 00000000..dd75e100 --- /dev/null +++ b/packages/module/src/Tearsheet/Tearsheet.tsx @@ -0,0 +1,72 @@ +import type { ReactNode, HTMLProps, FunctionComponent, MouseEvent } from 'react'; +import { css } from '@patternfly/react-styles'; +import styles from '@patternfly/react-styles/css/components/Tearsheet/tearsheet'; +import { Modal, ModalVariant } from '@patternfly/react-core'; + +export interface TearsheetProps extends HTMLProps { + /** Content rendered inside the tearsheet. Should be TearsheetHeader, TearsheetBody, and/or TearsheetFooter. */ + children: ReactNode; + /** Additional classes added to the tearsheet. */ + className?: string; + /** Flag to show the tearsheet. */ + isOpen?: boolean; + /** Visual stack level of the tearsheet. Managed automatically by TearsheetGroup. + * When used standalone: 0 (back), 1 (middle), 2 (front). + * TearsheetGroup may also assign -1 (hidden behind the stack). */ + stackLevel?: number; + /** A callback for when the close button is clicked. This prop needs to be passed to render the close button. */ + onClose?: (event: KeyboardEvent | MouseEvent) => void; + /** A callback for when the tearsheet is closed via the escape key. */ + onEscapePress?: (event: KeyboardEvent) => void; + /** The parent container to append the tearsheet to. Defaults to document.body. */ + appendTo?: HTMLElement | (() => HTMLElement); + /** Accessible label for the tearsheet. */ + 'aria-label'?: string; + /** ID of the element that labels the tearsheet. */ + 'aria-labelledby'?: string; + /** ID of the element that describes the tearsheet. */ + 'aria-describedby'?: string; + /** Flag to disable focus trap. */ + disableFocusTrap?: boolean; +} + +const Tearsheet: FunctionComponent = ({ + children, + className, + isOpen = false, + stackLevel: stackLevelProp, + onClose, + onEscapePress, + appendTo, + 'aria-label': ariaLabel, + 'aria-labelledby': ariaLabelledBy, + 'aria-describedby': ariaDescribedBy, + disableFocusTrap, + ...props +}: TearsheetProps) => { + const stackLevel = stackLevelProp ?? 0; + const stackLevelClassname = stackLevel < 0 ? 'pf-m-stack-hidden' : `pf-m-stack-level-${stackLevel}`; + + return ( + +
+ {children} +
+
+ ); +}; +Tearsheet.displayName = 'Tearsheet'; + +export default Tearsheet; diff --git a/packages/module/src/Tearsheet/index.ts b/packages/module/src/Tearsheet/index.ts new file mode 100644 index 00000000..4342d637 --- /dev/null +++ b/packages/module/src/Tearsheet/index.ts @@ -0,0 +1,2 @@ +export { default } from './Tearsheet'; +export * from './Tearsheet'; diff --git a/packages/module/src/Tearsheet/tearsheet.css b/packages/module/src/Tearsheet/tearsheet.css new file mode 100644 index 00000000..904ffa25 --- /dev/null +++ b/packages/module/src/Tearsheet/tearsheet.css @@ -0,0 +1,87 @@ +.pf-v6-c-tearsheet { + width: calc(100% - 4rem) !important; + max-width: calc(100% - 4rem) !important; + height: calc(100% - 4rem) !important; + max-height: calc(100% - 4rem) !important; + inset-block-start: 0 !important; + top: 2rem !important; +} + +/* Override the modal animation custom property to compose stack-level transitions + with the existing open/close animation. Specificity 0,2,0 beats the single-class + declarations in modal-animations.css. */ +.pf-v6-c-modal-animated.pf-v6-c-tearsheet { + --pf-v6-c-modal-animated--Transition: + width 300ms ease, + max-width 300ms ease, + height 300ms ease, + max-height 300ms ease, + top 300ms ease, + opacity 240ms cubic-bezier(0.4, 0.14, 1, 1), + transform 240ms cubic-bezier(0.4, 0.14, 1, 1), + visibility 0ms linear 240ms; +} + +.pf-v6-c-modal-animated-open.pf-v6-c-tearsheet { + --pf-v6-c-modal-animated--Transition: + width 300ms ease, + max-width 300ms ease, + height 300ms ease, + max-height 300ms ease, + top 300ms ease, + transform 240ms cubic-bezier(0, 0, 0.2, 1), + visibility 0ms linear 0ms; +} + +.pf-v6-c-tearsheet.pf-m-stack-level-1 { + width: calc(100% - 2rem) !important; + max-width: calc(100% - 2rem) !important; + height: calc(100% - 6rem) !important; + max-height: calc(100% - 6rem) !important; + inset-block-start: 0 !important; + top: 3rem !important; +} + +.pf-v6-c-tearsheet.pf-m-stack-level-2 { + width: calc(100% - 0rem) !important; + max-width: calc(100% - 0rem) !important; + height: calc(100% - 8rem) !important; + max-height: calc(100% - 8rem) !important; + inset-block-start: 0 !important; + top: 4rem !important; +} + +.pf-v6-c-tearsheet.pf-m-stack-hidden { + width: calc(100% - 4rem) !important; + max-width: calc(100% - 4rem) !important; + height: calc(100% - 4rem) !important; + max-height: calc(100% - 4rem) !important; + inset-block-start: 0 !important; + top: 3rem !important; + opacity: 0 !important; + pointer-events: none !important; +} + +.pf-v6-c-tearsheet-inner { + display: flex; + flex-direction: column; + overflow: hidden; + height: 100%; + margin-inline-end: 0; +} + +.pf-v6-c-tearsheet-header { + flex-shrink: 0; +} + +.pf-v6-c-tearsheet-body { + caret-color: red; +} + +.pf-v6-c-tearsheet-footer { + flex-shrink: 0; +} + +.pf-v6-c-tearsheet-group { + caret-color: red; +} diff --git a/packages/module/src/TearsheetBody/TearsheetBody.tsx b/packages/module/src/TearsheetBody/TearsheetBody.tsx new file mode 100644 index 00000000..7103a869 --- /dev/null +++ b/packages/module/src/TearsheetBody/TearsheetBody.tsx @@ -0,0 +1,15 @@ +import type { FunctionComponent } from 'react'; +import { css } from '@patternfly/react-styles'; +import styles from '@patternfly/react-styles/css/components/Tearsheet/tearsheet'; +import { ModalBody, type ModalBodyProps } from '@patternfly/react-core'; + +export interface TearsheetBodyProps extends ModalBodyProps { + className: string; +} + +const TearsheetBody: FunctionComponent = ({ className, ...props }: TearsheetBodyProps) => ( + +); +TearsheetBody.displayName = 'TearsheetBody'; + +export default TearsheetBody; \ No newline at end of file diff --git a/packages/module/src/TearsheetBody/index.ts b/packages/module/src/TearsheetBody/index.ts new file mode 100644 index 00000000..7229a1b8 --- /dev/null +++ b/packages/module/src/TearsheetBody/index.ts @@ -0,0 +1,2 @@ +export { default } from './TearsheetBody'; +export * from './TearsheetBody'; diff --git a/packages/module/src/TearsheetFooter/TearsheetFooter.tsx b/packages/module/src/TearsheetFooter/TearsheetFooter.tsx new file mode 100644 index 00000000..2ac25bc9 --- /dev/null +++ b/packages/module/src/TearsheetFooter/TearsheetFooter.tsx @@ -0,0 +1,16 @@ +import type { FunctionComponent } from 'react'; +import { css } from '@patternfly/react-styles'; +import styles from '@patternfly/react-styles/css/components/Tearsheet/tearsheet'; +import { ModalFooter, type ModalFooterProps } from '@patternfly/react-core'; + +export interface TearsheetFooterProps extends ModalFooterProps { + className: string; +} + +export const TearsheetFooter: FunctionComponent = ({ + className, + ...props +}: TearsheetFooterProps) => ; +TearsheetFooter.displayName = 'TearsheetFooter'; + +export default TearsheetFooter; diff --git a/packages/module/src/TearsheetFooter/index.ts b/packages/module/src/TearsheetFooter/index.ts new file mode 100644 index 00000000..97946ba7 --- /dev/null +++ b/packages/module/src/TearsheetFooter/index.ts @@ -0,0 +1,2 @@ +export { default } from './TearsheetFooter'; +export * from './TearsheetFooter'; diff --git a/packages/module/src/TearsheetGroup/TearsheetGroup.tsx b/packages/module/src/TearsheetGroup/TearsheetGroup.tsx new file mode 100644 index 00000000..41d61b7e --- /dev/null +++ b/packages/module/src/TearsheetGroup/TearsheetGroup.tsx @@ -0,0 +1,82 @@ +import { Children, cloneElement, isValidElement, useRef, type FunctionComponent, type ReactElement } from 'react'; +import { css } from '@patternfly/react-styles'; +import styles from '@patternfly/react-styles/css/components/Tearsheet/tearsheet'; +import { Tearsheet, type TearsheetProps } from '@patternfly/react-core'; + +/** The maximum number of visually distinct stack levels (0, 1, 2). */ +const MAX_VISIBLE_LEVELS = 3; + +export interface TearsheetGroupProps { + /** Set of Tearsheets to render inside the group. Render order determines stacking + * priority — later children stack in front of earlier ones. Only Tearsheets with + * isOpen={true} participate in the visual stack. */ + children?: React.ReactNode; + /** Additional classes added to the Tearsheet group. */ + className?: string; + /** Unique id for the Tearsheet group. */ + id: string; +} + +const TearsheetGroup: FunctionComponent = ({ + children, + className, + id, + ...props +}: TearsheetGroupProps) => { + // Track each child's last assigned stack level so closing tearsheets keep + // their position during the modal exit animation instead of snapping to L0. + const prevLevelsRef = useRef>(new Map()); + + const openIndices: number[] = []; + Children.forEach(children, (child, index) => { + if (isValidElement(child) && child.type === Tearsheet && (child.props as TearsheetProps).isOpen) { + openIndices.push(index); + } + }); + + const totalOpen = openIndices.length; + const hiddenThreshold = Math.max(0, totalOpen - MAX_VISIBLE_LEVELS); + + const enhancedChildren = Children.map(children, (child, index) => { + if (!isValidElement(child) || child.type !== Tearsheet) { + return child; + } + + const openPosition = openIndices.indexOf(index); + + if (openPosition === -1) { + // Use the last known level if this tearsheet was previously open, so the + // modal close animation plays without a conflicting size/position shift. + // For never-opened tearsheets, prime them at the level they'd occupy if + // they opened next (the frontmost slot). The tearsheet is invisible at + // this point, so the pre-sizing has no visual effect — but it prevents a + // width/height/top transition from firing alongside the modal enter + // animation when the tearsheet does open. + const level = prevLevelsRef.current.get(index) ?? Math.min(totalOpen, MAX_VISIBLE_LEVELS - 1); + return cloneElement(child as ReactElement, { + stackLevel: level + }); + } + + // Levels fill from 0 upward: 1 open → L0, 2 open → L0+L1, 3 open → L0+L1+L2. + // Once all 3 visible slots are used, earlier tearsheets hide behind the stack. + const stackLevel = openPosition < hiddenThreshold ? -1 : openPosition - hiddenThreshold; + const isFrontmost = openPosition === totalOpen - 1; + + prevLevelsRef.current.set(index, stackLevel); + + return cloneElement(child as ReactElement, { + stackLevel, + disableFocusTrap: !isFrontmost + }); + }); + + return ( +
+ {enhancedChildren} +
+ ); +}; +TearsheetGroup.displayName = 'TearsheetGroup'; + +export default TearsheetGroup; diff --git a/packages/module/src/TearsheetGroup/index.ts b/packages/module/src/TearsheetGroup/index.ts new file mode 100644 index 00000000..a6827ad9 --- /dev/null +++ b/packages/module/src/TearsheetGroup/index.ts @@ -0,0 +1,2 @@ +export { default } from './TearsheetGroup'; +export * from './TearsheetGroup'; diff --git a/packages/module/src/TearsheetHeader/TearsheetHeader.tsx b/packages/module/src/TearsheetHeader/TearsheetHeader.tsx new file mode 100644 index 00000000..92ca5782 --- /dev/null +++ b/packages/module/src/TearsheetHeader/TearsheetHeader.tsx @@ -0,0 +1,16 @@ +import type { FunctionComponent } from 'react'; +import { css } from '@patternfly/react-styles'; +import styles from '@patternfly/react-styles/css/components/Tearsheet/tearsheet'; +import { ModalHeader, type ModalHeaderProps } from '@patternfly/react-core'; + +export interface TearsheetHeaderProps extends ModalHeaderProps { + className: string; +} + +const TearsheetHeader: FunctionComponent = ({ + className, + ...props +}: TearsheetHeaderProps) => ; +TearsheetHeader.displayName = 'TearsheetHeader'; + +export default TearsheetHeader; diff --git a/packages/module/src/TearsheetHeader/index.ts b/packages/module/src/TearsheetHeader/index.ts new file mode 100644 index 00000000..eb954f6d --- /dev/null +++ b/packages/module/src/TearsheetHeader/index.ts @@ -0,0 +1,2 @@ +export { default } from './TearsheetHeader'; +export * from './TearsheetHeader'; From 05ab49f988fb507654a37379bf860a2340806219 Mon Sep 17 00:00:00 2001 From: Gustavo Murcia Date: Wed, 8 Jul 2026 16:33:41 -0400 Subject: [PATCH 2/9] chore: Migrate tearsheet.css to proper JSS use Generated-by: Claude Opus 4.6 Co-authored-by: Claude Opus 4.6 --- packages/module/src/Tearsheet/Tearsheet.tsx | 61 +++++++++++++++++-- .../src/TearsheetBody/TearsheetBody.tsx | 15 +++-- .../src/TearsheetFooter/TearsheetFooter.tsx | 13 +++- .../src/TearsheetGroup/TearsheetGroup.tsx | 13 +++- .../src/TearsheetHeader/TearsheetHeader.tsx | 13 +++- 5 files changed, 100 insertions(+), 15 deletions(-) diff --git a/packages/module/src/Tearsheet/Tearsheet.tsx b/packages/module/src/Tearsheet/Tearsheet.tsx index dd75e100..37691d83 100644 --- a/packages/module/src/Tearsheet/Tearsheet.tsx +++ b/packages/module/src/Tearsheet/Tearsheet.tsx @@ -1,8 +1,60 @@ import type { ReactNode, HTMLProps, FunctionComponent, MouseEvent } from 'react'; import { css } from '@patternfly/react-styles'; -import styles from '@patternfly/react-styles/css/components/Tearsheet/tearsheet'; +import { createUseStyles } from 'react-jss'; import { Modal, ModalVariant } from '@patternfly/react-core'; +const useStyles = createUseStyles({ + tearsheet: { + width: 'calc(100% - 4rem) !important', + maxWidth: 'calc(100% - 4rem) !important', + height: 'calc(100% - 4rem) !important', + maxHeight: 'calc(100% - 4rem) !important', + insetBlockStart: '0 !important', + top: '2rem !important', + '&.pf-v6-c-modal-animated': { + '--pf-v6-c-modal-animated--Transition': + 'width 300ms ease, max-width 300ms ease, height 300ms ease, max-height 300ms ease, top 300ms ease, opacity 240ms cubic-bezier(0.4, 0.14, 1, 1), transform 240ms cubic-bezier(0.4, 0.14, 1, 1), visibility 0ms linear 240ms', + }, + '&.pf-v6-c-modal-animated-open': { + '--pf-v6-c-modal-animated--Transition': + 'width 300ms ease, max-width 300ms ease, height 300ms ease, max-height 300ms ease, top 300ms ease, transform 240ms cubic-bezier(0, 0, 0.2, 1), visibility 0ms linear 0ms', + }, + '&.pf-m-stack-level-1': { + width: 'calc(100% - 2rem) !important', + maxWidth: 'calc(100% - 2rem) !important', + height: 'calc(100% - 6rem) !important', + maxHeight: 'calc(100% - 6rem) !important', + insetBlockStart: '0 !important', + top: '3rem !important', + }, + '&.pf-m-stack-level-2': { + width: 'calc(100% - 0rem) !important', + maxWidth: 'calc(100% - 0rem) !important', + height: 'calc(100% - 8rem) !important', + maxHeight: 'calc(100% - 8rem) !important', + insetBlockStart: '0 !important', + top: '4rem !important', + }, + '&.pf-m-stack-hidden': { + width: 'calc(100% - 4rem) !important', + maxWidth: 'calc(100% - 4rem) !important', + height: 'calc(100% - 4rem) !important', + maxHeight: 'calc(100% - 4rem) !important', + insetBlockStart: '0 !important', + top: '3rem !important', + opacity: '0 !important', + pointerEvents: 'none !important', + }, + }, + tearsheetInner: { + display: 'flex', + flexDirection: 'column', + overflow: 'hidden', + height: '100%', + marginInlineEnd: 0, + }, +}); + export interface TearsheetProps extends HTMLProps { /** Content rendered inside the tearsheet. Should be TearsheetHeader, TearsheetBody, and/or TearsheetFooter. */ children: ReactNode; @@ -44,13 +96,14 @@ const Tearsheet: FunctionComponent = ({ disableFocusTrap, ...props }: TearsheetProps) => { + const classes = useStyles(); const stackLevel = stackLevelProp ?? 0; const stackLevelClassname = stackLevel < 0 ? 'pf-m-stack-hidden' : `pf-m-stack-level-${stackLevel}`; return ( = ({ appendTo={appendTo} disableFocusTrap={disableFocusTrap} > -
+
{children}
diff --git a/packages/module/src/TearsheetBody/TearsheetBody.tsx b/packages/module/src/TearsheetBody/TearsheetBody.tsx index 7103a869..8ed06efe 100644 --- a/packages/module/src/TearsheetBody/TearsheetBody.tsx +++ b/packages/module/src/TearsheetBody/TearsheetBody.tsx @@ -1,15 +1,22 @@ import type { FunctionComponent } from 'react'; import { css } from '@patternfly/react-styles'; -import styles from '@patternfly/react-styles/css/components/Tearsheet/tearsheet'; +import { createUseStyles } from 'react-jss'; import { ModalBody, type ModalBodyProps } from '@patternfly/react-core'; +const useStyles = createUseStyles({ + tearsheetBody: { + caretColor: 'red', + }, +}); + export interface TearsheetBodyProps extends ModalBodyProps { className: string; } -const TearsheetBody: FunctionComponent = ({ className, ...props }: TearsheetBodyProps) => ( - -); +const TearsheetBody: FunctionComponent = ({ className, ...props }: TearsheetBodyProps) => { + const classes = useStyles(); + return ; +}; TearsheetBody.displayName = 'TearsheetBody'; export default TearsheetBody; \ No newline at end of file diff --git a/packages/module/src/TearsheetFooter/TearsheetFooter.tsx b/packages/module/src/TearsheetFooter/TearsheetFooter.tsx index 2ac25bc9..03732aa5 100644 --- a/packages/module/src/TearsheetFooter/TearsheetFooter.tsx +++ b/packages/module/src/TearsheetFooter/TearsheetFooter.tsx @@ -1,8 +1,14 @@ import type { FunctionComponent } from 'react'; import { css } from '@patternfly/react-styles'; -import styles from '@patternfly/react-styles/css/components/Tearsheet/tearsheet'; +import { createUseStyles } from 'react-jss'; import { ModalFooter, type ModalFooterProps } from '@patternfly/react-core'; +const useStyles = createUseStyles({ + tearsheetFooter: { + flexShrink: 0, + }, +}); + export interface TearsheetFooterProps extends ModalFooterProps { className: string; } @@ -10,7 +16,10 @@ export interface TearsheetFooterProps extends ModalFooterProps { export const TearsheetFooter: FunctionComponent = ({ className, ...props -}: TearsheetFooterProps) => ; +}: TearsheetFooterProps) => { + const classes = useStyles(); + return ; +}; TearsheetFooter.displayName = 'TearsheetFooter'; export default TearsheetFooter; diff --git a/packages/module/src/TearsheetGroup/TearsheetGroup.tsx b/packages/module/src/TearsheetGroup/TearsheetGroup.tsx index 41d61b7e..34c1f3d8 100644 --- a/packages/module/src/TearsheetGroup/TearsheetGroup.tsx +++ b/packages/module/src/TearsheetGroup/TearsheetGroup.tsx @@ -1,7 +1,13 @@ import { Children, cloneElement, isValidElement, useRef, type FunctionComponent, type ReactElement } from 'react'; import { css } from '@patternfly/react-styles'; -import styles from '@patternfly/react-styles/css/components/Tearsheet/tearsheet'; -import { Tearsheet, type TearsheetProps } from '@patternfly/react-core'; +import { createUseStyles } from 'react-jss'; +import Tearsheet, { type TearsheetProps } from '../Tearsheet'; + +const useStyles = createUseStyles({ + tearsheetGroup: { + caretColor: 'red', + }, +}); /** The maximum number of visually distinct stack levels (0, 1, 2). */ const MAX_VISIBLE_LEVELS = 3; @@ -23,6 +29,7 @@ const TearsheetGroup: FunctionComponent = ({ id, ...props }: TearsheetGroupProps) => { + const classes = useStyles(); // Track each child's last assigned stack level so closing tearsheets keep // their position during the modal exit animation instead of snapping to L0. const prevLevelsRef = useRef>(new Map()); @@ -72,7 +79,7 @@ const TearsheetGroup: FunctionComponent = ({ }); return ( -
+
{enhancedChildren}
); diff --git a/packages/module/src/TearsheetHeader/TearsheetHeader.tsx b/packages/module/src/TearsheetHeader/TearsheetHeader.tsx index 92ca5782..457c8a10 100644 --- a/packages/module/src/TearsheetHeader/TearsheetHeader.tsx +++ b/packages/module/src/TearsheetHeader/TearsheetHeader.tsx @@ -1,8 +1,14 @@ import type { FunctionComponent } from 'react'; import { css } from '@patternfly/react-styles'; -import styles from '@patternfly/react-styles/css/components/Tearsheet/tearsheet'; +import { createUseStyles } from 'react-jss'; import { ModalHeader, type ModalHeaderProps } from '@patternfly/react-core'; +const useStyles = createUseStyles({ + tearsheetHeader: { + flexShrink: 0, + }, +}); + export interface TearsheetHeaderProps extends ModalHeaderProps { className: string; } @@ -10,7 +16,10 @@ export interface TearsheetHeaderProps extends ModalHeaderProps { const TearsheetHeader: FunctionComponent = ({ className, ...props -}: TearsheetHeaderProps) => ; +}: TearsheetHeaderProps) => { + const classes = useStyles(); + return ; +}; TearsheetHeader.displayName = 'TearsheetHeader'; export default TearsheetHeader; From af55c44a07e89ae538a7a6de9a9969dbd73541a0 Mon Sep 17 00:00:00 2001 From: Gustavo Murcia Date: Wed, 8 Jul 2026 16:59:42 -0400 Subject: [PATCH 3/9] chore: Port doc examples --- .../examples/Tearsheet/Tearsheet.md | 49 ++++++++ .../examples/Tearsheet/TearsheetBasic.tsx | 43 +++++++ .../examples/Tearsheet/TearsheetGroup.tsx | 66 ++++++++++ .../examples/Tearsheet/TearsheetLayouts.tsx | 117 ++++++++++++++++++ .../examples/Tearsheet/TearsheetStacked.tsx | 101 +++++++++++++++ 5 files changed, 376 insertions(+) create mode 100644 packages/module/patternfly-docs/content/extensions/component-groups/examples/Tearsheet/Tearsheet.md create mode 100644 packages/module/patternfly-docs/content/extensions/component-groups/examples/Tearsheet/TearsheetBasic.tsx create mode 100644 packages/module/patternfly-docs/content/extensions/component-groups/examples/Tearsheet/TearsheetGroup.tsx create mode 100644 packages/module/patternfly-docs/content/extensions/component-groups/examples/Tearsheet/TearsheetLayouts.tsx create mode 100644 packages/module/patternfly-docs/content/extensions/component-groups/examples/Tearsheet/TearsheetStacked.tsx diff --git a/packages/module/patternfly-docs/content/extensions/component-groups/examples/Tearsheet/Tearsheet.md b/packages/module/patternfly-docs/content/extensions/component-groups/examples/Tearsheet/Tearsheet.md new file mode 100644 index 00000000..ae68c6e0 --- /dev/null +++ b/packages/module/patternfly-docs/content/extensions/component-groups/examples/Tearsheet/Tearsheet.md @@ -0,0 +1,49 @@ +--- +# Sidenav top-level section +# should be the same for all markdown files +section: extensions +subsection: component-groups +# Sidenav secondary level section +# should be the same for all markdown files +id: Tearsheet +# Tab (react | react-demos | html | html-demos | design-guidelines | accessibility) +source: react +# If you use typescript, the name of the interface to display props for +# These are found through the sourceProps function provided in patternfly-docs.source.js +propComponents: ['Tearsheet'] +sourceLink: https://github.com/patternfly/react-component-groups/blob/main/packages/module/patternfly-docs/content/extensions/component-groups/examples/Tearsheet/Tearsheet.md +--- + +import { Fragment, useState } from 'react'; +import spacing from '@patternfly/react-styles/css/utilities/Spacing/spacing'; +import TearsheetGroup from '@patternfly/react-component-groups/dist/dynamic/TearsheetGroup'; +import Tearsheet from '@patternfly/react-component-groups/dist/dynamic/Tearsheet'; +import TearsheetHeader from '@patternfly/react-component-groups/dist/dynamic/TearsheetHeader'; +import TearsheetBody from '@patternfly/react-component-groups/dist/dynamic/TearsheetBody'; +import TearsheetFooter from '@patternfly/react-component-groups/dist/dynamic/TearsheetFooter'; + +Tearsheet is used for ... + +## Examples + +### Basic + +```ts file="./TearsheetBasic.tsx" +``` + +### Stacked + +```ts file="./TearsheetStacked.tsx" +``` + +### Tearsheet group (infinite stacking) + +Use a `TearsheetGroup` to manage an unbounded number of stacked tearsheets. Render order determines stacking priority — later children stack in front of earlier ones. Only the top 3 open tearsheets are visible; earlier ones hide behind the stack and reappear as front tearsheets are closed. + +```ts file="./TearsheetGroup.tsx" +``` + +### Tearsheet layouts + +```ts file="./TearsheetLayouts.tsx" +``` diff --git a/packages/module/patternfly-docs/content/extensions/component-groups/examples/Tearsheet/TearsheetBasic.tsx b/packages/module/patternfly-docs/content/extensions/component-groups/examples/Tearsheet/TearsheetBasic.tsx new file mode 100644 index 00000000..e25e1655 --- /dev/null +++ b/packages/module/patternfly-docs/content/extensions/component-groups/examples/Tearsheet/TearsheetBasic.tsx @@ -0,0 +1,43 @@ +import { Fragment, useState } from 'react'; +import { Button } from '@patternfly/react-core'; +import Tearsheet from '@patternfly/react-component-groups/dist/dynamic/Tearsheet'; +import TearsheetHeader from '@patternfly/react-component-groups/dist/dynamic/TearsheetHeader'; +import TearsheetBody from '@patternfly/react-component-groups/dist/dynamic/TearsheetBody'; +import TearsheetFooter from '@patternfly/react-component-groups/dist/dynamic/TearsheetFooter'; + +export const TearsheetBasic: React.FunctionComponent = () => { + const [ isTearsheetOpen, setIsTearsheetOpen ] = useState(false); + + const toggleTearsheet = (_event: React.MouseEvent | KeyboardEvent | MouseEvent) => { + setIsTearsheetOpen(!isTearsheetOpen); + }; + + return ( + + + | KeyboardEvent | MouseEvent) => toggleTearsheet(e)} + > + + + Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore + magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo + consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla + pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id + est laborum. + + + + + + + + ); +}; diff --git a/packages/module/patternfly-docs/content/extensions/component-groups/examples/Tearsheet/TearsheetGroup.tsx b/packages/module/patternfly-docs/content/extensions/component-groups/examples/Tearsheet/TearsheetGroup.tsx new file mode 100644 index 00000000..0f10d2f8 --- /dev/null +++ b/packages/module/patternfly-docs/content/extensions/component-groups/examples/Tearsheet/TearsheetGroup.tsx @@ -0,0 +1,66 @@ +import { useState } from 'react'; +import { Button } from '@patternfly/react-core'; +import TearsheetGroup from '@patternfly/react-component-groups/dist/dynamic/TearsheetGroup'; +import Tearsheet from '@patternfly/react-component-groups/dist/dynamic/Tearsheet'; +import TearsheetHeader from '@patternfly/react-component-groups/dist/dynamic/TearsheetHeader'; +import TearsheetBody from '@patternfly/react-component-groups/dist/dynamic/TearsheetBody'; +import TearsheetFooter from '@patternfly/react-component-groups/dist/dynamic/TearsheetFooter'; + +const TOTAL_TEARSHEETS = 10; + +export const TearsheetGroupExample: React.FunctionComponent = () => { + const [ openState, setOpenState ] = useState(Array(TOTAL_TEARSHEETS).fill(false)); + + const open = (index: number) => { + setOpenState((prev) => { + const next = [ ...prev ]; + next[index] = true; + return next; + }); + }; + + const close = (index: number) => { + setOpenState((prev) => { + const next = [ ...prev ]; + next[index] = false; + return next; + }); + }; + + return ( +
+
+ +
+ + + {Array.from({ length: TOTAL_TEARSHEETS }, (_, i) => ( + close(i)} aria-label={`Tearsheet ${i + 1}`}> + + +

+ This is tearsheet #{i + 1} of {TOTAL_TEARSHEETS}. +

+

+ The TearsheetGroup manages stacking automatically. Only the top 3 open tearsheets are visible in the + stack — earlier ones hide behind and reappear as you close the ones in front. +

+
+ + {i < TOTAL_TEARSHEETS - 1 && ( + + )} + + +
+ ))} +
+
+ ); +}; diff --git a/packages/module/patternfly-docs/content/extensions/component-groups/examples/Tearsheet/TearsheetLayouts.tsx b/packages/module/patternfly-docs/content/extensions/component-groups/examples/Tearsheet/TearsheetLayouts.tsx new file mode 100644 index 00000000..50e44503 --- /dev/null +++ b/packages/module/patternfly-docs/content/extensions/component-groups/examples/Tearsheet/TearsheetLayouts.tsx @@ -0,0 +1,117 @@ +import { Fragment, useState } from 'react'; +import { + Button, + Card, + CardBody, + CardHeader, + CardTitle, + Content, + DescriptionList, + DescriptionListDescription, + DescriptionListGroup, + DescriptionListTerm, + Divider, + Title +} from '@patternfly/react-core'; +import Tearsheet from '@patternfly/react-component-groups/dist/dynamic/Tearsheet'; +import TearsheetHeader from '@patternfly/react-component-groups/dist/dynamic/TearsheetHeader'; +import TearsheetBody from '@patternfly/react-component-groups/dist/dynamic/TearsheetBody'; +import TearsheetFooter from '@patternfly/react-component-groups/dist/dynamic/TearsheetFooter'; +import { Flex, FlexItem, Grid, GridItem } from '@patternfly/react-core'; + +type BodyLayout = 'simple' | 'xl-text' | 'grid' | 'long'; + +const LOREM = + 'Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore ' + + 'magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo ' + + 'consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla ' + + 'pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id ' + + 'est laborum.'; + +const bodyLayouts: Record React.ReactNode }> = { + simple: { + label: 'Simple text', + render: () => {LOREM} + }, + 'xl-text': { + label: 'XL text', + render: () => ( + <> + {Array.from({ length: 30 }, (_, i) => ( + + {LOREM} + + ))} + + ) + }, + grid: { + label: 'Grid layout', + render: () => ( + + {[ 'Overview', 'Configuration', 'Resources', 'Networking', 'Storage', 'Monitoring' ].map((title) => ( + + + + {title} + + {LOREM.slice(0, 120)}... + + + ))} + + ) + }, + long: { + label: 'Long layout', + render: () => ( + + {[ 'General', 'Details', 'Configuration', 'Permissions', 'Audit log' ].map((section) => ( + + {section} + + + {[ 'Name', 'Status', 'Created', 'Modified' ].map((term) => ( + + {term} + {LOREM.slice(0, 80)} + + ))} + + + ))} + + ) + } +}; + +export const TearsheetLayouts: React.FunctionComponent = () => { + const [ activeLayout, setActiveLayout ] = useState(null); + + const open = (layout: BodyLayout) => () => setActiveLayout(layout); + const close = (_event: React.MouseEvent | KeyboardEvent | MouseEvent) => setActiveLayout(null); + + return ( + + + {(Object.keys(bodyLayouts) as BodyLayout[]).map((key) => ( + + ))} + + + + {activeLayout && bodyLayouts[activeLayout].render()} + + + + + + + ); +}; diff --git a/packages/module/patternfly-docs/content/extensions/component-groups/examples/Tearsheet/TearsheetStacked.tsx b/packages/module/patternfly-docs/content/extensions/component-groups/examples/Tearsheet/TearsheetStacked.tsx new file mode 100644 index 00000000..c2f35398 --- /dev/null +++ b/packages/module/patternfly-docs/content/extensions/component-groups/examples/Tearsheet/TearsheetStacked.tsx @@ -0,0 +1,101 @@ +import { Fragment, useState } from 'react'; +import { Button } from '@patternfly/react-core'; +import Tearsheet from '@patternfly/react-component-groups/dist/dynamic/Tearsheet'; +import TearsheetHeader from '@patternfly/react-component-groups/dist/dynamic/TearsheetHeader'; +import TearsheetBody from '@patternfly/react-component-groups/dist/dynamic/TearsheetBody'; +import TearsheetFooter from '@patternfly/react-component-groups/dist/dynamic/TearsheetFooter'; + +export const TearsheetStacked: React.FunctionComponent = () => { + const [ isTearsheetOpen, setIsTearsheetOpen ] = useState(false); + const [ isStack1TearsheetOpen, setIsStack1TearsheetOpen ] = useState(false); + const [ isStack2TearsheetOpen, setIsStack2TearsheetOpen ] = useState(false); + + const toggleTearsheet = (_event: React.MouseEvent | KeyboardEvent | MouseEvent) => { + setIsTearsheetOpen(!isTearsheetOpen); + }; + const toggleStack1Tearsheet = (_event: React.MouseEvent | KeyboardEvent | MouseEvent) => { + setIsStack1TearsheetOpen(!isStack1TearsheetOpen); + }; + const toggleStack2Tearsheet = (_event: React.MouseEvent | KeyboardEvent | MouseEvent) => { + setIsStack2TearsheetOpen(!isStack2TearsheetOpen); + }; + + return ( + + + | KeyboardEvent | MouseEvent) => toggleTearsheet(e)} + > + + + Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore + magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo + consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla + pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id + est laborum. + + + + + + + + | KeyboardEvent | MouseEvent) => toggleStack1Tearsheet(e)} + > + + + Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore + magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo + consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla + pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id + est laborum. + + + + + + + + | KeyboardEvent | MouseEvent) => toggleStack2Tearsheet(e)} + > + + + Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore + magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo + consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla + pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id + est laborum. + + + + + + + + ); +}; From d93b8fec446abe8e91fa4f1c8d1c2aabebd01b4a Mon Sep 17 00:00:00 2001 From: Gustavo Murcia Date: Wed, 8 Jul 2026 17:52:31 -0400 Subject: [PATCH 4/9] fix: Build os-agnostic MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit On Windows, Node's path.resolve and path.relative produce backslash-separated paths (e.g. C:\src\*\index.ts). glob v10+ treats backslashes as escape characters rather than path separators, so \* becomes a literal asterisk match instead of a wildcard. This caused generate-index.js to find zero source files, producing an empty src/index.ts, which compiled to an empty dist/esm/index.js. The downstream build:fed:packages step then crashed with "Cannot read properties of undefined (reading 'flags')" when the TypeScript checker tried to get exports from a module with no symbol. Even if the index had been populated, generate-fed-package-json.js had the same glob issue — its patterns with process.cwd() backslashes would match nothing on Windows, so no dist/dynamic/*/package.json stubs would be created. The doc examples import from those stubs, so the dev server would still show a blank page. Additionally, path.relative on Windows returns backslash paths, which broke the .replace('/dist', '') calls that strip the dist prefix from relative paths written into generated package.json files. Changes: - Add packages/module/utils.js with cross-platform path utilities: toPosixPath (normalize separators), posixGlobSync (normalize glob pattern + results), and posixRelative (normalize path.relative output). Each function is documented with why it exists. - Refactor generate-index.js to use posixGlobSync from utils.js - Refactor generate-fed-package-json.js to use toPosixPath, posixGlobSync, and posixRelative from utils.js - Normalize basePath and path.relative output inline in scripts/parse-dynamic-modules.mjs (kept inline since it lives in a separate shared scripts directory) All changes are no-ops on Linux/macOS where paths already use forward slashes. Generated-by: Claude Co-authored-by: Claude --- packages/module/generate-fed-package-json.js | 24 ++++++------- packages/module/generate-index.js | 4 +-- packages/module/src/index.ts | 15 ++++++++ packages/module/utils.js | 36 ++++++++++++++++++++ scripts/parse-dynamic-modules.mjs | 6 ++-- 5 files changed, 69 insertions(+), 16 deletions(-) create mode 100644 packages/module/utils.js diff --git a/packages/module/generate-fed-package-json.js b/packages/module/generate-fed-package-json.js index c14056fa..42933f14 100644 --- a/packages/module/generate-fed-package-json.js +++ b/packages/module/generate-fed-package-json.js @@ -1,14 +1,14 @@ const fse = require('fs-extra'); -const { globSync } = require('glob'); const path = require('path'); const { default: getDynamicModuleMap } = require('../../scripts/parse-dynamic-modules.mjs'); +const { toPosixPath, posixGlobSync, posixRelative } = require('./utils'); -const root = process.cwd(); +const root = toPosixPath(process.cwd()); -const sourceFiles = globSync(`${root}/src/*/`) +const sourceFiles = posixGlobSync(`${root}/src/*/`) .map((name) => name.replace(/\/$/, '')); - -const indexTypings = globSync(`${root}/src/index.d.ts`); + +const indexTypings = posixGlobSync(`${root}/src/index.d.ts`); const ENV_AGNOSTIC_ROOT = `${root}/dist/dynamic` @@ -23,9 +23,9 @@ async function copyTypings(files, dest) { async function createPackage(file) { const fileName = file.split('/').pop(); - const esmSource = globSync(`${root}/dist/esm/${fileName}/**/index.js`)[0]; - const cjsSource = globSync(`${root}/dist/cjs/${fileName}/**/index.js`)[0]; - const typingsSource = globSync(`${root}/dist/esm/${fileName}/**/index.d.ts`)[0] + const esmSource = posixGlobSync(`${root}/dist/esm/${fileName}/**/index.js`)[0]; + const cjsSource = posixGlobSync(`${root}/dist/cjs/${fileName}/**/index.js`)[0]; + const typingsSource = posixGlobSync(`${root}/dist/esm/${fileName}/**/index.d.ts`)[0] /** * Prevent creating package.json for directories with no JS files (like CSS directories) */ @@ -39,14 +39,14 @@ async function createPackage(file) { // ensure the directory exists fse.ensureDirSync(destDir) - const esmRelative = path.relative(file, esmSource).replace('/dist', ''); - const cjsRelative = path.relative(file, cjsSource).replace('/dist', ''); - const tsRelative = path.relative(file, typingsSource).replace('/dist', '') + const esmRelative = posixRelative(file, esmSource).replace('/dist', ''); + const cjsRelative = posixRelative(file, cjsSource).replace('/dist', ''); + const tsRelative = posixRelative(file, typingsSource).replace('/dist', '') const content = { main: cjsRelative, module: esmRelative, }; - const typings = globSync(`${root}/src/${fileName}/*.d.ts`); + const typings = posixGlobSync(`${root}/src/${fileName}/*.d.ts`); const cmds = []; content.typings = tsRelative; cmds.push(copyTypings(typings, `${root}/dist/${fileName}`)); diff --git a/packages/module/generate-index.js b/packages/module/generate-index.js index 8ff8b416..3afa0b06 100644 --- a/packages/module/generate-index.js +++ b/packages/module/generate-index.js @@ -1,12 +1,12 @@ const fse = require('fs-extra'); -const { globSync } = require('glob'); const path = require('path'); +const { posixGlobSync } = require('./utils'); const root = process.cwd(); const ENV_AGNOSTIC_ROOT = `${root}/src` -const sourceFiles = globSync(path.resolve(__dirname, './src/*/index.ts')) +const sourceFiles = posixGlobSync(path.resolve(__dirname, './src/*/index.ts')) async function generateIndex(files) { // ensure the dynamic root exists diff --git a/packages/module/src/index.ts b/packages/module/src/index.ts index 4f23c2d4..c1ff0b47 100644 --- a/packages/module/src/index.ts +++ b/packages/module/src/index.ts @@ -9,6 +9,21 @@ export * from './UnavailableContent'; export { default as UnauthorizedAccess } from './UnauthorizedAccess'; export * from './UnauthorizedAccess'; +export { default as TearsheetHeader } from './TearsheetHeader'; +export * from './TearsheetHeader'; + +export { default as TearsheetGroup } from './TearsheetGroup'; +export * from './TearsheetGroup'; + +export { default as TearsheetFooter } from './TearsheetFooter'; +export * from './TearsheetFooter'; + +export { default as TearsheetBody } from './TearsheetBody'; +export * from './TearsheetBody'; + +export { default as Tearsheet } from './Tearsheet'; +export * from './Tearsheet'; + export { default as TagCount } from './TagCount'; export * from './TagCount'; diff --git a/packages/module/utils.js b/packages/module/utils.js new file mode 100644 index 00000000..e88e4ec2 --- /dev/null +++ b/packages/module/utils.js @@ -0,0 +1,36 @@ +/** + * Cross-platform path utilities for build scripts. + * + * On Windows, Node's path.resolve and path.relative produce backslash-separated + * paths. glob v10+ treats backslashes as escape characters (not path separators), + * so a pattern like `C:\src\*\index.ts` silently matches nothing because \* is + * interpreted as a literal asterisk. glob also returns backslash paths on Windows, + * breaking downstream code that splits on '/' to extract path segments. + * + * These utilities normalize paths to POSIX forward slashes for glob input/output + * and for any path strings written into generated package.json files. + */ + +const { globSync } = require('glob'); +const path = require('path'); + +/** Normalize a file path to use POSIX forward slashes. No-op on Linux/macOS. */ +const toPosixPath = (filePath) => filePath.replace(/\\/g, '/'); + +/** + * globSync wrapper that normalizes the pattern and results to POSIX paths. + * Use in place of globSync wherever the pattern includes Node-resolved paths + * (process.cwd(), path.resolve, __dirname, etc.) that may contain backslashes. + */ +const posixGlobSync = (pattern) => + globSync(toPosixPath(pattern)).map(toPosixPath); + +/** + * path.relative wrapper that returns a POSIX-style relative path. + * Use when the result will be written into a generated file (e.g. package.json + * "main"/"module" fields) where forward slashes are expected by consumers. + */ +const posixRelative = (from, to) => + toPosixPath(path.relative(from, to)); + +module.exports = { toPosixPath, posixGlobSync, posixRelative }; diff --git a/scripts/parse-dynamic-modules.mjs b/scripts/parse-dynamic-modules.mjs index 6a35cec7..97c88ba1 100644 --- a/scripts/parse-dynamic-modules.mjs +++ b/scripts/parse-dynamic-modules.mjs @@ -76,8 +76,10 @@ const getDynamicModuleMap = ( return {}; } + const normalizedBasePath = basePath.replace(/\\/g, '/'); + /** @type {Record} */ - const dynamicModulePathToPkgDir = glob.sync(`${basePath}/dist/dynamic/**/package.json`).reduce((acc, pkgFile) => { + const dynamicModulePathToPkgDir = glob.sync(`${normalizedBasePath}/dist/dynamic/**/package.json`).reduce((acc, pkgFile) => { const pkg = require(pkgFile); const pkgModule = pkg[resolutionField]; @@ -86,7 +88,7 @@ const getDynamicModuleMap = ( } const pkgResolvedPath = path.resolve(path.dirname(pkgFile), pkgModule); - const pkgRelativePath = path.dirname(path.relative(basePath, pkgFile)); + const pkgRelativePath = path.dirname(path.relative(basePath, pkgFile)).replace(/\\/g, '/'); acc[pkgResolvedPath] = pkgRelativePath; From 690e2fe080aa3f82b7559e983046e42544807922 Mon Sep 17 00:00:00 2001 From: Gustavo Murcia Date: Wed, 8 Jul 2026 18:19:10 -0400 Subject: [PATCH 5/9] fix: Build and props MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Doc site source discovery (patternfly-docs.source.js) passed raw path.join() output into globSync(). On Windows, path.join() produces backslash-separated paths (e.g. patternfly-docs\content\extensions\**\*.md). glob v10+ treats backslashes as escape characters, not path separators, so both the sourceProps and sourceMD globs silently matched zero files. This caused the generated/index.js routes file to be empty, leaving the entire Extensions sidenav blank on Windows dev servers. Fix: wrap glob patterns with the existing toPosixPath() utility from utils.js (created in d93b8fe for the same class of bug in other build scripts). Also mark className as optional in TearsheetBody, TearsheetFooter, and TearsheetHeader props interfaces — className is passed through to PF ModalBody/ModalFooter/ModalHeader which already default it. Generated-by: Claude Opus 4.6 Co-authored-by: Claude Opus 4.6 --- packages/module/patternfly-docs/patternfly-docs.source.js | 5 +++-- packages/module/src/TearsheetBody/TearsheetBody.tsx | 2 +- packages/module/src/TearsheetFooter/TearsheetFooter.tsx | 2 +- packages/module/src/TearsheetHeader/TearsheetHeader.tsx | 2 +- 4 files changed, 6 insertions(+), 5 deletions(-) diff --git a/packages/module/patternfly-docs/patternfly-docs.source.js b/packages/module/patternfly-docs/patternfly-docs.source.js index cff7aac9..f0127391 100644 --- a/packages/module/patternfly-docs/patternfly-docs.source.js +++ b/packages/module/patternfly-docs/patternfly-docs.source.js @@ -1,14 +1,15 @@ const path = require('path'); +const { toPosixPath } = require('../utils'); module.exports = (sourceMD, sourceProps) => { // Parse source content for props so that we can display them const propsIgnore = [ '**/*.test.tsx', '**/examples/*.tsx' ]; const extensionPath = path.join(__dirname, '../src'); - sourceProps(path.join(extensionPath, '/**/*.tsx'), propsIgnore); + sourceProps(toPosixPath(path.join(extensionPath, '/**/*.tsx')), propsIgnore); // Parse md files const contentBase = path.join(__dirname, './content'); - sourceMD(path.join(contentBase, 'extensions/**/*.md'), 'extensions'); + sourceMD(toPosixPath(path.join(contentBase, 'extensions/**/*.md')), 'extensions'); /** If you want to parse content from node_modules instead of providing a relative/absolute path, diff --git a/packages/module/src/TearsheetBody/TearsheetBody.tsx b/packages/module/src/TearsheetBody/TearsheetBody.tsx index 8ed06efe..6cf134d1 100644 --- a/packages/module/src/TearsheetBody/TearsheetBody.tsx +++ b/packages/module/src/TearsheetBody/TearsheetBody.tsx @@ -10,7 +10,7 @@ const useStyles = createUseStyles({ }); export interface TearsheetBodyProps extends ModalBodyProps { - className: string; + className?: string; } const TearsheetBody: FunctionComponent = ({ className, ...props }: TearsheetBodyProps) => { diff --git a/packages/module/src/TearsheetFooter/TearsheetFooter.tsx b/packages/module/src/TearsheetFooter/TearsheetFooter.tsx index 03732aa5..75db1d52 100644 --- a/packages/module/src/TearsheetFooter/TearsheetFooter.tsx +++ b/packages/module/src/TearsheetFooter/TearsheetFooter.tsx @@ -10,7 +10,7 @@ const useStyles = createUseStyles({ }); export interface TearsheetFooterProps extends ModalFooterProps { - className: string; + className?: string; } export const TearsheetFooter: FunctionComponent = ({ diff --git a/packages/module/src/TearsheetHeader/TearsheetHeader.tsx b/packages/module/src/TearsheetHeader/TearsheetHeader.tsx index 457c8a10..1c15fdc4 100644 --- a/packages/module/src/TearsheetHeader/TearsheetHeader.tsx +++ b/packages/module/src/TearsheetHeader/TearsheetHeader.tsx @@ -10,7 +10,7 @@ const useStyles = createUseStyles({ }); export interface TearsheetHeaderProps extends ModalHeaderProps { - className: string; + className?: string; } const TearsheetHeader: FunctionComponent = ({ From d0be61708a6d9a6b8bcd8847d6ed2c6c5cf1d44e Mon Sep 17 00:00:00 2001 From: Gustavo Murcia Date: Wed, 8 Jul 2026 18:44:13 -0400 Subject: [PATCH 6/9] fix: top not working with insetBlockStart because of JSS ordering Assisted-by: Claude Co-authored-by: Claude --- packages/module/src/Tearsheet/Tearsheet.tsx | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/packages/module/src/Tearsheet/Tearsheet.tsx b/packages/module/src/Tearsheet/Tearsheet.tsx index 37691d83..7aac8604 100644 --- a/packages/module/src/Tearsheet/Tearsheet.tsx +++ b/packages/module/src/Tearsheet/Tearsheet.tsx @@ -9,8 +9,8 @@ const useStyles = createUseStyles({ maxWidth: 'calc(100% - 4rem) !important', height: 'calc(100% - 4rem) !important', maxHeight: 'calc(100% - 4rem) !important', - insetBlockStart: '0 !important', - top: '2rem !important', + insetBlockStart: '2rem !important', + // top: '2rem !important', '&.pf-v6-c-modal-animated': { '--pf-v6-c-modal-animated--Transition': 'width 300ms ease, max-width 300ms ease, height 300ms ease, max-height 300ms ease, top 300ms ease, opacity 240ms cubic-bezier(0.4, 0.14, 1, 1), transform 240ms cubic-bezier(0.4, 0.14, 1, 1), visibility 0ms linear 240ms', @@ -24,24 +24,24 @@ const useStyles = createUseStyles({ maxWidth: 'calc(100% - 2rem) !important', height: 'calc(100% - 6rem) !important', maxHeight: 'calc(100% - 6rem) !important', - insetBlockStart: '0 !important', - top: '3rem !important', + insetBlockStart: '3rem !important', + // top: '3rem !important', }, '&.pf-m-stack-level-2': { width: 'calc(100% - 0rem) !important', maxWidth: 'calc(100% - 0rem) !important', height: 'calc(100% - 8rem) !important', maxHeight: 'calc(100% - 8rem) !important', - insetBlockStart: '0 !important', - top: '4rem !important', + insetBlockStart: '4rem !important', + // top: '4rem !important', }, '&.pf-m-stack-hidden': { width: 'calc(100% - 4rem) !important', maxWidth: 'calc(100% - 4rem) !important', height: 'calc(100% - 4rem) !important', maxHeight: 'calc(100% - 4rem) !important', - insetBlockStart: '0 !important', - top: '3rem !important', + insetBlockStart: '3rem !important', + // top: '3rem !important', opacity: '0 !important', pointerEvents: 'none !important', }, From 97ac9f550933052e75b5b055c34f8ef74792c73b Mon Sep 17 00:00:00 2001 From: Gustavo Murcia Date: Thu, 9 Jul 2026 13:12:30 -0400 Subject: [PATCH 7/9] chore: Tweaks - Bottom border radius, example docs, scrolling Tearsheet component: - Remove bottom border radius so tearsheet sits flush against viewport edge - Change tearsheetInner from height:100% to flex:1 1 auto + minHeight:0 so it participates properly in the ModalBox flex layout TearsheetBody component: - Replace placeholder caretColor:red with flex:1, minHeight:0, overflow:auto so the body fills remaining space and scrolls independently TearsheetGroup component: - Remove placeholder caretColor:red style Documentation (Tearsheet.md): - Expand propComponents to include TearsheetHeader, TearsheetBody, TearsheetFooter, and TearsheetGroup - Add descriptive text for each example section - Reorder examples: Basic, Layouts, Stacked, Group, Comparison TearsheetLayouts example: - Remove 'simple' layout, rename 'xl-text' to 'long-text' - Expand grid layout to 60 randomly sorted cards - Rename 'long' label to 'Flex layout' TearsheetComparison example (new): - Side-by-side Tearsheet vs Modal (ModalVariant.large) demo showing why tearsheets are better for dense content - Body content: sticky search bar (PageSection), vertical JumpLinks in a SidebarPanel, 3-column card grid with DescriptionLists, Labels, and CodeBlocks across 6 sections (36 cards total) - CSS fix for sidebar scrolling: sidebar__main height:100%, sidebar__content overflow:scroll + height:100% Generated-by: Claude Co-authored-by: Claude --- .../examples/Tearsheet/Tearsheet.md | 32 ++- .../Tearsheet/TearsheetComparison.tsx | 259 ++++++++++++++++++ .../examples/Tearsheet/TearsheetLayouts.tsx | 36 +-- packages/module/src/Tearsheet/Tearsheet.tsx | 5 +- .../src/TearsheetBody/TearsheetBody.tsx | 4 +- .../src/TearsheetGroup/TearsheetGroup.tsx | 1 - 6 files changed, 311 insertions(+), 26 deletions(-) create mode 100644 packages/module/patternfly-docs/content/extensions/component-groups/examples/Tearsheet/TearsheetComparison.tsx diff --git a/packages/module/patternfly-docs/content/extensions/component-groups/examples/Tearsheet/Tearsheet.md b/packages/module/patternfly-docs/content/extensions/component-groups/examples/Tearsheet/Tearsheet.md index ae68c6e0..08e1c00e 100644 --- a/packages/module/patternfly-docs/content/extensions/component-groups/examples/Tearsheet/Tearsheet.md +++ b/packages/module/patternfly-docs/content/extensions/component-groups/examples/Tearsheet/Tearsheet.md @@ -10,7 +10,7 @@ id: Tearsheet source: react # If you use typescript, the name of the interface to display props for # These are found through the sourceProps function provided in patternfly-docs.source.js -propComponents: ['Tearsheet'] +propComponents: ['Tearsheet', 'TearsheetHeader', 'TearsheetBody', 'TearsheetFooter', 'TearsheetGroup'] sourceLink: https://github.com/patternfly/react-component-groups/blob/main/packages/module/patternfly-docs/content/extensions/component-groups/examples/Tearsheet/Tearsheet.md --- @@ -22,28 +22,50 @@ import TearsheetHeader from '@patternfly/react-component-groups/dist/dynamic/Tea import TearsheetBody from '@patternfly/react-component-groups/dist/dynamic/TearsheetBody'; import TearsheetFooter from '@patternfly/react-component-groups/dist/dynamic/TearsheetFooter'; -Tearsheet is used for ... +**Tearsheet** are a full-screen extension of the `` component allowing more complex experiences to be provided to the user. +While the biggest Modal size (`ModalVariant.large`) may work for some cases, tearsheets allow near the entire real-estate to be leveraged. +This component extends the [modal component](/components/modal) allowing any use of its properties to be provided. ## Examples ### Basic +Typical tearsheets should make use of the entire area, for this basic case some sample text is rendered. + ```ts file="./TearsheetBasic.tsx" ``` +### Tearsheet layouts + +Tearsheets should allow various sorts of layouts to be rendered. +The `` component will handle scrolling for long content. + +```ts file="./TearsheetLayouts.tsx" +``` + ### Stacked +One special use case with tearsheets is stacking. +When a user is using a tearsheet, if another one needs to open it can open one level "on-top" of it in a new stack. +Tearsheets offer 3 stack levels (0,1,2). +A special stack level -1 allows a tearsheet to hide behind others. + ```ts file="./TearsheetStacked.tsx" ``` ### Tearsheet group (infinite stacking) -Use a `TearsheetGroup` to manage an unbounded number of stacked tearsheets. Render order determines stacking priority — later children stack in front of earlier ones. Only the top 3 open tearsheets are visible; earlier ones hide behind the stack and reappear as front tearsheets are closed. +Use a `TearsheetGroup` to manage an unbounded number of stacked tearsheets. +`children` rendering order determines stacking priority with later children stacking in front of earlier ones. +Only the top 3 open tearsheets are visible; earlier ones hide behind the stack and reappear as front tearsheets are closed. ```ts file="./TearsheetGroup.tsx" ``` -### Tearsheet layouts +### Tearsheets vs Modals -```ts file="./TearsheetLayouts.tsx" +To illustrate the difference between a tearsheet and a modal, this example showcases a complex use case with a search bar, side panel, and a number of cards. +In a modal the content is crammed and is not as usable as if it were on a bigger area like the tearsheet. + +```ts file="./TearsheetComparison.tsx" ``` diff --git a/packages/module/patternfly-docs/content/extensions/component-groups/examples/Tearsheet/TearsheetComparison.tsx b/packages/module/patternfly-docs/content/extensions/component-groups/examples/Tearsheet/TearsheetComparison.tsx new file mode 100644 index 00000000..0c761403 --- /dev/null +++ b/packages/module/patternfly-docs/content/extensions/component-groups/examples/Tearsheet/TearsheetComparison.tsx @@ -0,0 +1,259 @@ +import { Fragment, useState } from 'react'; +import { + Button, + Card, + CardBody, + CardHeader, + CardTitle, + CodeBlock, + CodeBlockCode, + Content, + DescriptionList, + DescriptionListDescription, + DescriptionListGroup, + DescriptionListTerm, + Flex, + Grid, + GridItem, + JumpLinks, + JumpLinksItem, + Label, + LabelGroup, + Modal, + ModalBody, + ModalFooter, + ModalHeader, + ModalVariant, + PageSection, + SearchInput, + Sidebar, + SidebarContent, + SidebarPanel +} from '@patternfly/react-core'; +import Tearsheet from '@patternfly/react-component-groups/dist/dynamic/Tearsheet'; +import TearsheetHeader from '@patternfly/react-component-groups/dist/dynamic/TearsheetHeader'; +import TearsheetBody from '@patternfly/react-component-groups/dist/dynamic/TearsheetBody'; +import TearsheetFooter from '@patternfly/react-component-groups/dist/dynamic/TearsheetFooter'; + +const SIDEBAR_FIX_CLASS = 'tearsheet-comparison-sidebar'; +const sidebarFixStyles = ` + .${SIDEBAR_FIX_CLASS} .pf-v6-c-sidebar__main { height: 100%; } + .${SIDEBAR_FIX_CLASS} .pf-v6-c-sidebar__content { overflow: scroll; height: 100%; } +`; + +const LOREM = + 'Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore ' + + 'magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo ' + + 'consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla ' + + 'pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id ' + + 'est laborum.'; + +const sections = [ + { + id: 'overview', + title: 'Overview', + description: 'High-level summary of the resource, including its purpose, current state, and key metadata.', + labels: [ 'v1', 'stable', 'core' ], + items: [ + { name: 'ConfigMap', status: 'Active', created: '2025-06-01', owner: 'platform-team' }, + { name: 'Secret', status: 'Active', created: '2025-06-02', owner: 'security-team' }, + { name: 'ServiceAccount', status: 'Active', created: '2025-05-28', owner: 'platform-team' }, + { name: 'Namespace', status: 'Terminating', created: '2025-04-15', owner: 'admin' }, + { name: 'LimitRange', status: 'Active', created: '2025-06-03', owner: 'ops-team' }, + { name: 'ResourceQuota', status: 'Active', created: '2025-06-03', owner: 'ops-team' } + ], + code: `apiVersion: v1\nkind: ConfigMap\nmetadata:\n name: my-config\n namespace: default\n labels:\n app.kubernetes.io/name: my-app\n app.kubernetes.io/version: "1.2.0"\n app.kubernetes.io/managed-by: helm\ndata:\n APP_ENV: production\n LOG_LEVEL: info\n MAX_CONNECTIONS: "100"` + }, + { + id: 'configuration', + title: 'Configuration', + description: 'Runtime parameters, feature flags, and environment-specific settings that control application behavior.', + labels: [ 'apps/v1', 'deployment', 'rolling-update' ], + items: [ + { name: 'Deployment', status: 'Available', created: '2025-06-01', owner: 'dev-team' }, + { name: 'StatefulSet', status: 'Ready', created: '2025-06-01', owner: 'data-team' }, + { name: 'DaemonSet', status: 'Scheduled', created: '2025-05-30', owner: 'infra-team' }, + { name: 'ReplicaSet', status: 'Available', created: '2025-06-01', owner: 'dev-team' }, + { name: 'CronJob', status: 'Suspended', created: '2025-05-20', owner: 'batch-team' }, + { name: 'Job', status: 'Complete', created: '2025-06-04', owner: 'batch-team' } + ], + code: `spec:\n replicas: 3\n selector:\n matchLabels:\n app: my-app\n strategy:\n type: RollingUpdate\n rollingUpdate:\n maxSurge: 1\n maxUnavailable: 0\n template:\n spec:\n containers:\n - name: app\n image: registry.io/my-app:1.2.0\n env:\n - name: DB_HOST\n valueFrom:\n secretKeyRef:\n name: db-credentials\n key: host` + }, + { + id: 'resources', + title: 'Resources', + description: 'CPU, memory, and storage allocations for each container in the workload.', + labels: [ 'requests', 'limits', 'QoS: Burstable' ], + items: [ + { name: 'app', status: 'Running', created: '2025-06-01', owner: 'dev-team' }, + { name: 'sidecar-proxy', status: 'Running', created: '2025-06-01', owner: 'mesh-team' }, + { name: 'log-collector', status: 'Running', created: '2025-06-01', owner: 'observability' }, + { name: 'init-db', status: 'Completed', created: '2025-06-01', owner: 'dev-team' }, + { name: 'init-config', status: 'Completed', created: '2025-06-01', owner: 'platform-team' }, + { name: 'debug', status: 'Waiting', created: '2025-06-05', owner: 'sre-team' } + ], + code: `containers:\n - name: app\n resources:\n requests:\n cpu: "250m"\n memory: "512Mi"\n ephemeral-storage: "1Gi"\n limits:\n cpu: "1"\n memory: "1Gi"\n ephemeral-storage: "2Gi"\n - name: sidecar-proxy\n resources:\n requests:\n cpu: "100m"\n memory: "128Mi"\n limits:\n cpu: "200m"\n memory: "256Mi"` + }, + { + id: 'networking', + title: 'Networking', + description: 'Service exposure, ingress rules, and network policies governing traffic flow.', + labels: [ 'ClusterIP', 'Ingress', 'NetworkPolicy' ], + items: [ + { name: 'my-service', status: 'Active', created: '2025-06-01', owner: 'dev-team' }, + { name: 'my-service-headless', status: 'Active', created: '2025-06-01', owner: 'data-team' }, + { name: 'ingress-main', status: 'Synced', created: '2025-06-02', owner: 'platform-team' }, + { name: 'netpol-deny-all', status: 'Enforcing', created: '2025-05-15', owner: 'security-team' }, + { name: 'netpol-allow-web', status: 'Enforcing', created: '2025-05-15', owner: 'security-team' }, + { name: 'external-dns', status: 'Active', created: '2025-06-03', owner: 'infra-team' } + ], + code: `apiVersion: networking.k8s.io/v1\nkind: Ingress\nmetadata:\n name: my-ingress\n annotations:\n nginx.ingress.kubernetes.io/rewrite-target: /\n cert-manager.io/cluster-issuer: letsencrypt\nspec:\n tls:\n - hosts:\n - app.example.com\n secretName: app-tls\n rules:\n - host: app.example.com\n http:\n paths:\n - path: /api\n pathType: Prefix\n backend:\n service:\n name: my-service\n port:\n number: 8080` + }, + { + id: 'storage', + title: 'Storage', + description: 'Persistent volume claims, storage classes, and mount configurations.', + labels: [ 'gp3', 'ReadWriteOnce', 'Retain' ], + items: [ + { name: 'data-pvc', status: 'Bound', created: '2025-06-01', owner: 'data-team' }, + { name: 'logs-pvc', status: 'Bound', created: '2025-06-01', owner: 'observability' }, + { name: 'backup-pvc', status: 'Bound', created: '2025-05-20', owner: 'ops-team' }, + { name: 'tmp-pvc', status: 'Pending', created: '2025-06-05', owner: 'dev-team' }, + { name: 'cache-emptydir', status: 'Mounted', created: '2025-06-01', owner: 'dev-team' }, + { name: 'config-projected', status: 'Mounted', created: '2025-06-01', owner: 'platform-team' } + ], + code: `apiVersion: v1\nkind: PersistentVolumeClaim\nmetadata:\n name: data-pvc\nspec:\n accessModes:\n - ReadWriteOnce\n storageClassName: gp3-encrypted\n resources:\n requests:\n storage: 50Gi\n---\nvolumeMounts:\n - name: data\n mountPath: /var/lib/data\n - name: logs\n mountPath: /var/log/app\n - name: cache\n mountPath: /tmp/cache\nvolumes:\n - name: data\n persistentVolumeClaim:\n claimName: data-pvc\n - name: cache\n emptyDir:\n sizeLimit: 500Mi` + }, + { + id: 'monitoring', + title: 'Monitoring', + description: 'Health checks, readiness probes, and metrics endpoints used for observability.', + labels: [ 'prometheus', 'liveness', 'readiness' ], + items: [ + { name: 'liveness-http', status: 'Passing', created: '2025-06-01', owner: 'dev-team' }, + { name: 'readiness-http', status: 'Passing', created: '2025-06-01', owner: 'dev-team' }, + { name: 'startup-tcp', status: 'Passing', created: '2025-06-01', owner: 'dev-team' }, + { name: 'metrics-endpoint', status: 'Scraping', created: '2025-06-02', owner: 'observability' }, + { name: 'alert-high-cpu', status: 'Firing', created: '2025-05-10', owner: 'sre-team' }, + { name: 'alert-error-rate', status: 'Pending', created: '2025-05-10', owner: 'sre-team' } + ], + code: `livenessProbe:\n httpGet:\n path: /healthz\n port: 8080\n initialDelaySeconds: 15\n periodSeconds: 10\n failureThreshold: 3\nreadinessProbe:\n httpGet:\n path: /readyz\n port: 8080\n initialDelaySeconds: 5\n periodSeconds: 5\nstartupProbe:\n tcpSocket:\n port: 8080\n failureThreshold: 30\n periodSeconds: 2\n---\napiVersion: monitoring.coreos.com/v1\nkind: ServiceMonitor\nmetadata:\n name: my-app-monitor\nspec:\n selector:\n matchLabels:\n app: my-app\n endpoints:\n - port: metrics\n interval: 15s\n path: /metrics` + } +]; + +const renderBodyContent = (sidebarClassName = '') => ( + + + + +
+ + + + {sections.map((s) => ( + e.preventDefault()}> + {s.title} + + ))} + + + + + {sections.map((section) => ( + + + + {section.title} + + {section.labels.map((l) => ( + + ))} + + + {section.description} + + {section.items.map((item, i) => ( + + + + {item.name} + + + + + Status + {item.status} + + + Created + {item.created} + + + Owner + {item.owner} + + + {LOREM.slice(0, 120)} + + {section.code} + + + + + ))} + + ))} + + + +
+
+); + +export const TearsheetComparison: React.FunctionComponent = () => { + const [ isTearsheetOpen, setIsTearsheetOpen ] = useState(false); + const [ isModalOpen, setIsModalOpen ] = useState(false); + + const closeTearsheet = () => setIsTearsheetOpen(false); + const closeModal = () => setIsModalOpen(false); + + return ( + + + + + + + + + + {renderBodyContent(SIDEBAR_FIX_CLASS)} + + + + + + + + + {renderBodyContent()} + + + + + + + ); +}; diff --git a/packages/module/patternfly-docs/content/extensions/component-groups/examples/Tearsheet/TearsheetLayouts.tsx b/packages/module/patternfly-docs/content/extensions/component-groups/examples/Tearsheet/TearsheetLayouts.tsx index 50e44503..33095bf7 100644 --- a/packages/module/patternfly-docs/content/extensions/component-groups/examples/Tearsheet/TearsheetLayouts.tsx +++ b/packages/module/patternfly-docs/content/extensions/component-groups/examples/Tearsheet/TearsheetLayouts.tsx @@ -19,7 +19,7 @@ import TearsheetBody from '@patternfly/react-component-groups/dist/dynamic/Tears import TearsheetFooter from '@patternfly/react-component-groups/dist/dynamic/TearsheetFooter'; import { Flex, FlexItem, Grid, GridItem } from '@patternfly/react-core'; -type BodyLayout = 'simple' | 'xl-text' | 'grid' | 'long'; +type BodyLayout = 'long-text' | 'grid' | 'long'; const LOREM = 'Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore ' + @@ -29,12 +29,8 @@ const LOREM = 'est laborum.'; const bodyLayouts: Record React.ReactNode }> = { - simple: { - label: 'Simple text', - render: () => {LOREM} - }, - 'xl-text': { - label: 'XL text', + 'long-text': { + label: 'Long text', render: () => ( <> {Array.from({ length: 30 }, (_, i) => ( @@ -49,21 +45,25 @@ const bodyLayouts: Record React.React label: 'Grid layout', render: () => ( - {[ 'Overview', 'Configuration', 'Resources', 'Networking', 'Storage', 'Monitoring' ].map((title) => ( - - - - {title} - - {LOREM.slice(0, 120)}... - - - ))} + {[ 'Overview', 'Configuration', 'Resources', 'Networking', 'Storage', 'Monitoring' ] + .map((title) => new Array(10).fill(title)) + .flat() + .sort(() => Math.random() - 0.5) + .map((title) => ( + + + + {title} + + {LOREM.slice(0, 120)}... + + + ))} ) }, long: { - label: 'Long layout', + label: 'Flex layout', render: () => ( {[ 'General', 'Details', 'Configuration', 'Permissions', 'Audit log' ].map((section) => ( diff --git a/packages/module/src/Tearsheet/Tearsheet.tsx b/packages/module/src/Tearsheet/Tearsheet.tsx index 7aac8604..2a137e01 100644 --- a/packages/module/src/Tearsheet/Tearsheet.tsx +++ b/packages/module/src/Tearsheet/Tearsheet.tsx @@ -11,6 +11,8 @@ const useStyles = createUseStyles({ maxHeight: 'calc(100% - 4rem) !important', insetBlockStart: '2rem !important', // top: '2rem !important', + borderBottomLeftRadius: '0', + borderBottomRightRadius: '0', '&.pf-v6-c-modal-animated': { '--pf-v6-c-modal-animated--Transition': 'width 300ms ease, max-width 300ms ease, height 300ms ease, max-height 300ms ease, top 300ms ease, opacity 240ms cubic-bezier(0.4, 0.14, 1, 1), transform 240ms cubic-bezier(0.4, 0.14, 1, 1), visibility 0ms linear 240ms', @@ -50,7 +52,8 @@ const useStyles = createUseStyles({ display: 'flex', flexDirection: 'column', overflow: 'hidden', - height: '100%', + flex: '1 1 auto', + minHeight: 0, marginInlineEnd: 0, }, }); diff --git a/packages/module/src/TearsheetBody/TearsheetBody.tsx b/packages/module/src/TearsheetBody/TearsheetBody.tsx index 6cf134d1..3fe30e29 100644 --- a/packages/module/src/TearsheetBody/TearsheetBody.tsx +++ b/packages/module/src/TearsheetBody/TearsheetBody.tsx @@ -5,7 +5,9 @@ import { ModalBody, type ModalBodyProps } from '@patternfly/react-core'; const useStyles = createUseStyles({ tearsheetBody: { - caretColor: 'red', + flex: 1, + minHeight: 0, + overflow: 'auto', }, }); diff --git a/packages/module/src/TearsheetGroup/TearsheetGroup.tsx b/packages/module/src/TearsheetGroup/TearsheetGroup.tsx index 34c1f3d8..e25d1246 100644 --- a/packages/module/src/TearsheetGroup/TearsheetGroup.tsx +++ b/packages/module/src/TearsheetGroup/TearsheetGroup.tsx @@ -5,7 +5,6 @@ import Tearsheet, { type TearsheetProps } from '../Tearsheet'; const useStyles = createUseStyles({ tearsheetGroup: { - caretColor: 'red', }, }); From 0047ae8b343a55ed155db911adec2197a97739e9 Mon Sep 17 00:00:00 2001 From: Gustavo Murcia Date: Thu, 9 Jul 2026 15:50:12 -0400 Subject: [PATCH 8/9] doc: Update syntax of code blocks --- README.md | 11 +++++------ 1 file changed, 5 insertions(+), 6 deletions(-) diff --git a/README.md b/README.md index b6507124..cd74b877 100644 --- a/README.md +++ b/README.md @@ -40,7 +40,7 @@ Please reference [PatternFly's AI-assisted development guidelines](https://githu #### Example component: -``` +``` TSX import * as React from 'react'; import { Content } from '@patternfly/react-core'; import { createUseStyles } from 'react-jss'; @@ -75,7 +75,7 @@ export default MyComponent; #### Index file example: -``` +``` TSX export { default } from './MyComponent'; export * from './MyComponent'; ``` @@ -99,8 +99,7 @@ src #### Component API definition example: -``` - +``` TSX import { FunctionComponent } from 'react'; // when possible, extend available PatternFly types @@ -113,7 +112,7 @@ export const MyComponent: FunctionComponent = ({ customLabel, #### Markdown file example: -```` +```` MDX --- section: Component groups subsection: My component's category @@ -135,7 +134,7 @@ MyComponent has been created to demo contributing to this repository. #### Component usage file example: (`MyComponentExample.tsx`) -``` +``` TSX import { FunctionComponent } from 'react'; const MyComponentExample: FunctionComponent = () => ( From fe6c124eab6bcfe00b49ce9298c4dbcb5237c3b2 Mon Sep 17 00:00:00 2001 From: Gustavo Murcia Date: Thu, 9 Jul 2026 20:31:19 -0400 Subject: [PATCH 9/9] chore: Cleanup --- packages/module/src/Tearsheet/tearsheet.css | 87 --------------------- 1 file changed, 87 deletions(-) delete mode 100644 packages/module/src/Tearsheet/tearsheet.css diff --git a/packages/module/src/Tearsheet/tearsheet.css b/packages/module/src/Tearsheet/tearsheet.css deleted file mode 100644 index 904ffa25..00000000 --- a/packages/module/src/Tearsheet/tearsheet.css +++ /dev/null @@ -1,87 +0,0 @@ -.pf-v6-c-tearsheet { - width: calc(100% - 4rem) !important; - max-width: calc(100% - 4rem) !important; - height: calc(100% - 4rem) !important; - max-height: calc(100% - 4rem) !important; - inset-block-start: 0 !important; - top: 2rem !important; -} - -/* Override the modal animation custom property to compose stack-level transitions - with the existing open/close animation. Specificity 0,2,0 beats the single-class - declarations in modal-animations.css. */ -.pf-v6-c-modal-animated.pf-v6-c-tearsheet { - --pf-v6-c-modal-animated--Transition: - width 300ms ease, - max-width 300ms ease, - height 300ms ease, - max-height 300ms ease, - top 300ms ease, - opacity 240ms cubic-bezier(0.4, 0.14, 1, 1), - transform 240ms cubic-bezier(0.4, 0.14, 1, 1), - visibility 0ms linear 240ms; -} - -.pf-v6-c-modal-animated-open.pf-v6-c-tearsheet { - --pf-v6-c-modal-animated--Transition: - width 300ms ease, - max-width 300ms ease, - height 300ms ease, - max-height 300ms ease, - top 300ms ease, - transform 240ms cubic-bezier(0, 0, 0.2, 1), - visibility 0ms linear 0ms; -} - -.pf-v6-c-tearsheet.pf-m-stack-level-1 { - width: calc(100% - 2rem) !important; - max-width: calc(100% - 2rem) !important; - height: calc(100% - 6rem) !important; - max-height: calc(100% - 6rem) !important; - inset-block-start: 0 !important; - top: 3rem !important; -} - -.pf-v6-c-tearsheet.pf-m-stack-level-2 { - width: calc(100% - 0rem) !important; - max-width: calc(100% - 0rem) !important; - height: calc(100% - 8rem) !important; - max-height: calc(100% - 8rem) !important; - inset-block-start: 0 !important; - top: 4rem !important; -} - -.pf-v6-c-tearsheet.pf-m-stack-hidden { - width: calc(100% - 4rem) !important; - max-width: calc(100% - 4rem) !important; - height: calc(100% - 4rem) !important; - max-height: calc(100% - 4rem) !important; - inset-block-start: 0 !important; - top: 3rem !important; - opacity: 0 !important; - pointer-events: none !important; -} - -.pf-v6-c-tearsheet-inner { - display: flex; - flex-direction: column; - overflow: hidden; - height: 100%; - margin-inline-end: 0; -} - -.pf-v6-c-tearsheet-header { - flex-shrink: 0; -} - -.pf-v6-c-tearsheet-body { - caret-color: red; -} - -.pf-v6-c-tearsheet-footer { - flex-shrink: 0; -} - -.pf-v6-c-tearsheet-group { - caret-color: red; -}