Skip to content
Merged
Show file tree
Hide file tree
Changes from 7 commits
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.27",
"@patternfly/patternfly": "6.5.0-prerelease.33",
"case-anything": "^3.1.2",
"css": "^3.0.0",
"fs-extra": "^11.3.0"
Expand Down
68 changes: 42 additions & 26 deletions packages/react-core/src/components/Compass/Compass.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,8 @@ import compassBackgroundImageDark from '@patternfly/react-tokens/dist/esm/c_comp
export interface CompassProps extends React.HTMLProps<HTMLDivElement> {
/** Additional classes added to the Compass. */
className?: string;
/** Content of the docked navigation area of the layout */
dock?: React.ReactNode;
/** Content placed at the top of the layout */
header?: React.ReactNode;
/** Flag indicating if the header is expanded */
Expand Down Expand Up @@ -38,6 +40,7 @@ export interface CompassProps extends React.HTMLProps<HTMLDivElement> {

export const Compass: React.FunctionComponent<CompassProps> = ({
className,
dock,
header,
isHeaderExpanded = true,
sidebarStart,
Expand All @@ -64,32 +67,45 @@ export const Compass: React.FunctionComponent<CompassProps> = ({
}

const compassContent = (
<div className={css(styles.compass, className)} {...props} style={{ ...props.style, ...backgroundImageStyles }}>
<div
className={css(styles.compassHeader, isHeaderExpanded && 'pf-m-expanded')}
{...(!isHeaderExpanded && { inert: 'true' })}
>
{header}
</div>
<div
className={css(styles.compassSidebar, styles.modifiers.start, isSidebarStartExpanded && 'pf-m-expanded')}
{...(!isSidebarStartExpanded && { inert: 'true' })}
>
{sidebarStart}
</div>
<div className={css(styles.compassMain)}>{main}</div>
<div
className={css(styles.compassSidebar, styles.modifiers.end, isSidebarEndExpanded && 'pf-m-expanded')}
{...(!isSidebarEndExpanded && { inert: 'true' })}
>
{sidebarEnd}
</div>
<div
className={css(styles.compassFooter, isFooterExpanded && 'pf-m-expanded')}
{...(!isFooterExpanded && { inert: 'true' })}
>
{footer}
</div>
<div
className={css(styles.compass, dock !== undefined && styles.modifiers.dock, className)}
{...props}
style={{ ...props.style, ...backgroundImageStyles }}
>
{dock && <div className={css(`${styles.compass}__dock`)}>{dock}</div>}
{header && (
<div
className={css(styles.compassHeader, isHeaderExpanded && 'pf-m-expanded')}
{...(!isHeaderExpanded && { inert: 'true' })}
>
{header}
</div>
)}
{sidebarStart && (
<div
className={css(styles.compassSidebar, styles.modifiers.start, isSidebarStartExpanded && 'pf-m-expanded')}
{...(!isSidebarStartExpanded && { inert: 'true' })}
>
{sidebarStart}
</div>
)}
{main && <div className={css(styles.compassMain)}>{main}</div>}
{sidebarEnd && (
<div
className={css(styles.compassSidebar, styles.modifiers.end, isSidebarEndExpanded && 'pf-m-expanded')}
{...(!isSidebarEndExpanded && { inert: 'true' })}
>
{sidebarEnd}
</div>
)}
{footer && (
<div
className={css(styles.compassFooter, isFooterExpanded && 'pf-m-expanded')}
{...(!isFooterExpanded && { inert: 'true' })}
>
{footer}
</div>
)}
</div>
);

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -170,3 +170,13 @@ test('Matches the snapshot with drawer', () => {
);
expect(asFragment()).toMatchSnapshot();
});

test(`Renders with ${styles.modifiers.dock} class when dock is passed`, () => {
render(<Compass dock={<div>Dock content</div>} data-testid="compass" />);
expect(screen.getByTestId('compass')).toHaveClass(styles.modifiers.dock);
});

test(`Does not render with ${styles.modifiers.dock} class when dock is not passed`, () => {
render(<Compass data-testid="compass" />);
expect(screen.getByTestId('compass')).not.toHaveClass(styles.modifiers.dock);
});
21 changes: 20 additions & 1 deletion packages/react-core/src/components/Compass/examples/Compass.md
Original file line number Diff line number Diff line change
Expand Up @@ -17,11 +17,18 @@ propComponents:
]
---

import { useRef, useState } from 'react';
import { useRef, useState, useEffect } from 'react';
import PlayIcon from '@patternfly/react-icons/dist/esm/icons/play-icon';
import OutlinedPlusSquare from '@patternfly/react-icons/dist/esm/icons/outlined-plus-square-icon';
import OutlinedCopy from '@patternfly/react-icons/dist/esm/icons/outlined-copy-icon';
import OutlinedQuestionCircleIcon from '@patternfly/react-icons/dist/esm/icons/outlined-question-circle-icon';
import CubeIcon from '@patternfly/react-icons/dist/esm/icons/cube-icon';
import FolderIcon from '@patternfly/react-icons/dist/esm/icons/folder-icon';
import QuestionCircleIcon from '@patternfly/react-icons/dist/esm/icons/question-circle-icon';
import CloudIcon from '@patternfly/react-icons/dist/esm/icons/cloud-icon';
import CodeIcon from '@patternfly/react-icons/dist/esm/icons/code-icon';
import imgAvatar from '../../assets/avatarImg.svg';
import pfLogo from '../../assets/PF-IconLogo-color.svg';

import './compass.css';

Expand Down Expand Up @@ -51,6 +58,18 @@ When `footer` is used, its content will fill the width of the screen. By default

```

### With docked nav

```ts file="CompassDockLayout.tsx"

```

### Docked nav demo

```ts isFullscreen file="CompassDockDemo.tsx"

```

## Composable structure

When building a more custom implementation with Compass components, there are some intended or expected structures that must remain present.
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,197 @@
import { useRef, useState } from 'react';
import {
Compass,
CompassContent,
CompassMainHeader,
CompassPanel,
Title,
NavItem,
NavList,
Nav,
Brand,
MastheadLogo,
MastheadBrand,
MastheadContent,
MastheadMain,
Masthead,
Toolbar,
ToolbarContent,
ToolbarItem,
ToolbarGroup,
Dropdown,
DropdownList,
MenuToggle,
MenuToggleElement,
DropdownItem,
Button,
ButtonVariant,
Avatar,
Tooltip,
Divider
} from '@patternfly/react-core';
import CubeIcon from '@patternfly/react-icons/dist/esm/icons/cube-icon';
import FolderIcon from '@patternfly/react-icons/dist/esm/icons/folder-icon';
import QuestionCircleIcon from '@patternfly/react-icons/dist/esm/icons/question-circle-icon';
import CloudIcon from '@patternfly/react-icons/dist/esm/icons/cloud-icon';
import CodeIcon from '@patternfly/react-icons/dist/esm/icons/code-icon';
import pfLogo from '../../assets/PF-IconLogo-color.svg';
import imgAvatar from '../../assets/avatarImg.svg';

interface NavOnSelectProps {
groupId: number | string;
itemId: number | string;
to: string;
}

export const CompassDockDemo: React.FunctionComponent = () => {
const [activeItem, setActiveItem] = useState<number>(0);
const [isDropdownOpen, setIsDropdownOpen] = useState(false);

const onNavSelect = (_event: React.FormEvent<HTMLInputElement>, selectedItem: NavOnSelectProps) => {
typeof selectedItem.itemId === 'number' && setActiveItem(selectedItem.itemId);
};

const onDropdownToggle = () => {
setIsDropdownOpen(!isDropdownOpen);
};

const onDropdownSelect = () => {
setIsDropdownOpen(!isDropdownOpen);
};

const userDropdownItems = [
<>
<DropdownItem key="group 2 profile">My profile</DropdownItem>
<DropdownItem key="group 2 user">User management</DropdownItem>
<DropdownItem key="group 2 logout">Logout</DropdownItem>
</>
];

const navItem1Ref = useRef<HTMLAnchorElement>(null);
const navItem2Ref = useRef<HTMLAnchorElement>(null);
const navItem3Ref = useRef<HTMLAnchorElement>(null);
const navItem4Ref = useRef<HTMLAnchorElement>(null);
const dockContent = (
<Masthead id="icon-router-link" variant="docked">
<MastheadMain>
<MastheadBrand>
<MastheadLogo component={(props) => <a {...props} href="#" />}>
<Brand src={pfLogo} alt="PatternFly" heights={{ default: '36px' }} />
</MastheadLogo>
</MastheadBrand>
</MastheadMain>
<Divider />
<MastheadContent>
<Toolbar id="toolbar" isVertical>
<ToolbarContent>
<ToolbarItem>
<Nav onSelect={onNavSelect} variant="docked" aria-label="Icon global" ouiaId="IconNav">
<NavList>
<NavItem
key="nav-icon-link1"
preventDefault
id="nav-icon-link1"
to="#nav-icon-link1"
itemId={0}
isActive={activeItem === 0}
icon={<CubeIcon />}
ref={navItem1Ref}
aria-label="Link 1"
/>
<NavItem
key="nav-icon-link2"
preventDefault
id="nav-icon-link2"
to="#nav-icon-link2"
itemId={1}
isActive={activeItem === 1}
icon={<FolderIcon />}
ref={navItem2Ref}
aria-label="Link 2"
/>
<NavItem
key="nav-icon-link3"
preventDefault
id="nav-icon-link3"
to="#nav-icon-link3"
itemId={2}
isActive={activeItem === 2}
icon={<CloudIcon />}
ref={navItem3Ref}
aria-label="Link 3"
/>
<NavItem
key="nav-icon-link4"
preventDefault
id="nav-icon-link4"
to="#nav-icon-link4"
itemId={3}
isActive={activeItem === 3}
icon={<CodeIcon />}
ref={navItem4Ref}
aria-label="Link 4"
/>
</NavList>
</Nav>
<Tooltip triggerRef={navItem1Ref} content="Link 1"></Tooltip>
<Tooltip triggerRef={navItem2Ref} content="Link 2"></Tooltip>
<Tooltip triggerRef={navItem3Ref} content="Link 3"></Tooltip>
<Tooltip triggerRef={navItem4Ref} content="Link 4"></Tooltip>
</ToolbarItem>
<ToolbarGroup
variant="action-group-plain"
align={{ default: 'alignEnd' }}
gap={{ default: 'gapNone', md: 'gapMd' }}
>
<ToolbarGroup variant="action-group-plain" visibility={{ default: 'hidden', lg: 'visible' }}>
<ToolbarItem>
<Button aria-label="Settings" isSettings variant="plain" />
</ToolbarItem>
<ToolbarItem>
<Button aria-label="Help" variant={ButtonVariant.plain} icon={<QuestionCircleIcon />} />
</ToolbarItem>
</ToolbarGroup>
</ToolbarGroup>
<ToolbarItem>
<Dropdown
isOpen={isDropdownOpen}
onSelect={onDropdownSelect}
onOpenChange={(isOpen: boolean) => setIsDropdownOpen(isOpen)}
toggle={(toggleRef: React.Ref<MenuToggleElement>) => (
<MenuToggle
ref={toggleRef}
onClick={onDropdownToggle}
isExpanded={isDropdownOpen}
icon={<Avatar src={imgAvatar} alt="" size="sm" />}
variant="plain"
aria-label="User menu"
></MenuToggle>
)}
>
<DropdownList>{userDropdownItems}</DropdownList>
</Dropdown>
</ToolbarItem>
</ToolbarContent>
</Toolbar>
</MastheadContent>
</Masthead>
);

const mainContent = (
<>
<CompassMainHeader title={<Title headingLevel="h1">Content title</Title>} />
<CompassContent>
<CompassPanel>Content</CompassPanel>
</CompassContent>
</>
);

return (
<Compass
dock={dockContent}
main={mainContent}
backgroundSrcDark="/assets/images/pf-background.svg"
backgroundSrcLight="/assets/images/pf-background.svg"
/>
);
};
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
import {
Compass,
CompassContent,
CompassMainHeader,
CompassPanel,
CompassMainHeaderContent
} from '@patternfly/react-core';
import './compass.css';

export const CompassBasic: React.FunctionComponent = () => {
const dockContent = <div>Content</div>;
const mainContent = (
<CompassContent>
<CompassMainHeader>
<CompassPanel>
<CompassMainHeaderContent>
<div>Content title</div>
</CompassMainHeaderContent>
</CompassPanel>
</CompassMainHeader>
<div>Content</div>
</CompassContent>
);
return <Compass dock={dockContent} main={mainContent} />;
};
12 changes: 12 additions & 0 deletions packages/react-core/src/components/Compass/examples/compass.css
Original file line number Diff line number Diff line change
Expand Up @@ -20,4 +20,16 @@
inset: 0;
border: var(--pf-t--global--border--width--regular) dashed var(--pf-t--global--border--color--default);
pointer-events: none;
}

#ws-react-a-compass-with-docked-nav [class*="pf-v6-c-compass"] {
position: relative;
}

#ws-react-a-compass-with-docked-nav [class*="pf-v6-c-compass"]:not([class*="footer"])::after {
content: "";
position: absolute;
inset: 0;
border: var(--pf-t--global--border--width--regular) dashed var(--pf-t--global--border--color--default);
pointer-events: none;
}
Loading
Loading