Skip to content

Commit 7f152d0

Browse files
authored
Masthead (#6170)
* masthead initial commit * change brand to anchor, add example * add display, insets, bgColor * remove non-composable alternative, add demos, add unit tests, add integration demo, add full height to dropdown and context selector * add description to Masthead docs stating the common structure * flip to TS examples/demos * update link example to use icon * className set to undefined * rebase and add modifiers to toolbar * add beta tag * use Brand, update kebab position * add tabindex
1 parent 7a3f8ce commit 7f152d0

File tree

19 files changed

+1329
-1
lines changed

19 files changed

+1329
-1
lines changed

packages/react-core/src/components/ContextSelector/ContextSelector.tsx

+9-1
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,8 @@ export interface ContextSelectorProps extends ToggleMenuBaseProps, OUIAProps {
2929
onToggle?: (event: any, value: boolean) => void;
3030
/** Function callback called when user selects item */
3131
onSelect?: (event: any, value: React.ReactNode) => void;
32+
/** Flag indicating that the context selector should expand to full height */
33+
isFullHeight?: boolean;
3234
/** Labels the Context Selector for Screen Readers */
3335
screenReaderLabel?: string;
3436
/** Text that appears in the Context Selector Toggle */
@@ -95,6 +97,7 @@ export class ContextSelector extends React.Component<ContextSelectorProps, { oui
9597
children,
9698
className,
9799
isOpen,
100+
isFullHeight,
98101
onToggle,
99102
onSelect,
100103
screenReaderLabel,
@@ -155,7 +158,12 @@ export class ContextSelector extends React.Component<ContextSelectorProps, { oui
155158
);
156159
const mainContainer = (
157160
<div
158-
className={css(styles.contextSelector, isOpen && styles.modifiers.expanded, className)}
161+
className={css(
162+
styles.contextSelector,
163+
isOpen && styles.modifiers.expanded,
164+
isFullHeight && styles.modifiers.fullHeight,
165+
className
166+
)}
159167
ref={this.parentRef}
160168
{...getOUIAProps(ContextSelector.displayName, ouiaId !== undefined ? ouiaId : this.state.ouiaStateId, ouiaSafe)}
161169
{...props}

packages/react-core/src/components/Dropdown/Dropdown.tsx

+2
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,8 @@ export interface DropdownProps extends ToggleMenuBaseProps, React.HTMLProps<HTML
1616
isOpen?: boolean;
1717
/** Display the toggle with no border or background */
1818
isPlain?: boolean;
19+
/** Flag indicating that the dropdown should expand to full height */
20+
isFullHeight?: boolean;
1921
/** Indicates where menu will be aligned horizontally */
2022
position?: DropdownPosition | 'right' | 'left';
2123
/** Indicates how the menu will align at screen size breakpoints. Default alignment is set via the position property. */

packages/react-core/src/components/Dropdown/DropdownWithContext.tsx

+2
Original file line numberDiff line numberDiff line change
@@ -66,6 +66,7 @@ export class DropdownWithContext extends React.Component<DropdownProps & OUIAPro
6666
isOpen,
6767
isPlain,
6868
isGrouped,
69+
isFullHeight,
6970
// eslint-disable-next-line @typescript-eslint/no-unused-vars
7071
onSelect,
7172
position,
@@ -126,6 +127,7 @@ export class DropdownWithContext extends React.Component<DropdownProps & OUIAPro
126127
direction === DropdownDirection.up && styles.modifiers.top,
127128
position === DropdownPosition.right && styles.modifiers.alignRight,
128129
isOpen && styles.modifiers.expanded,
130+
isFullHeight && styles.modifiers.fullHeight,
129131
className
130132
)}
131133
ref={this.baseComponentRef}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,54 @@
1+
import * as React from 'react';
2+
import styles from '@patternfly/react-styles/css/components/Masthead/masthead';
3+
import { css } from '@patternfly/react-styles';
4+
import { formatBreakpointMods } from '../../helpers/util';
5+
export interface MastheadProps extends React.DetailedHTMLProps<React.HTMLProps<HTMLDivElement>, HTMLDivElement> {
6+
/** Content rendered inside of the masthead */
7+
children?: React.ReactNode;
8+
/** Additional classes added to the masthead */
9+
className?: string;
10+
/** Background theme color of the masthead */
11+
backgroundColor?: 'dark' | 'light' | 'light200';
12+
/** Display type at various breakpoints */
13+
display?: {
14+
default?: 'inline' | 'stack';
15+
sm?: 'inline' | 'stack';
16+
md?: 'inline' | 'stack';
17+
lg?: 'inline' | 'stack';
18+
xl?: 'inline' | 'stack';
19+
'2xl'?: 'inline' | 'stack';
20+
};
21+
/** Insets at various breakpoints */
22+
inset?: {
23+
default?: 'insetNone' | 'insetXs' | 'insetSm' | 'insetMd' | 'insetLg' | 'insetXl' | 'inset2xl' | 'inset3xl';
24+
sm?: 'insetNone' | 'insetXs' | 'insetSm' | 'insetMd' | 'insetLg' | 'insetXl' | 'inset2xl' | 'inset3xl';
25+
md?: 'insetNone' | 'insetXs' | 'insetSm' | 'insetMd' | 'insetLg' | 'insetXl' | 'inset2xl' | 'inset3xl';
26+
lg?: 'insetNone' | 'insetXs' | 'insetSm' | 'insetMd' | 'insetLg' | 'insetXl' | 'inset2xl' | 'inset3xl';
27+
xl?: 'insetNone' | 'insetXs' | 'insetSm' | 'insetMd' | 'insetLg' | 'insetXl' | 'inset2xl' | 'inset3xl';
28+
'2xl'?: 'insetNone' | 'insetXs' | 'insetSm' | 'insetMd' | 'insetLg' | 'insetXl' | 'inset2xl' | 'inset3xl';
29+
};
30+
}
31+
32+
export const Masthead: React.FunctionComponent<MastheadProps> = ({
33+
children,
34+
className,
35+
backgroundColor = 'dark',
36+
display,
37+
inset,
38+
...props
39+
}: MastheadProps) => (
40+
<header
41+
className={css(
42+
styles.masthead,
43+
formatBreakpointMods(display, styles, 'display-'),
44+
formatBreakpointMods(inset, styles),
45+
backgroundColor === 'light' && styles.modifiers.light,
46+
backgroundColor === 'light200' && styles.modifiers.light_200,
47+
className
48+
)}
49+
{...props}
50+
>
51+
{children}
52+
</header>
53+
);
54+
Masthead.displayName = 'Masthead';
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,28 @@
1+
import * as React from 'react';
2+
import styles from '@patternfly/react-styles/css/components/Masthead/masthead';
3+
import { css } from '@patternfly/react-styles';
4+
5+
export interface MastheadBrandProps
6+
extends React.DetailedHTMLProps<React.HTMLProps<HTMLAnchorElement>, HTMLAnchorElement> {
7+
/** Content rendered inside of the masthead brand. */
8+
children?: React.ReactNode;
9+
/** Additional classes added to the masthead brand. */
10+
className?: string;
11+
/** Component type of the masthead brand. */
12+
component?: React.ElementType<any> | React.ComponentType<any>;
13+
}
14+
15+
export const MastheadBrand: React.FunctionComponent<MastheadBrandProps> = ({
16+
children,
17+
className,
18+
component = 'a',
19+
...props
20+
}: MastheadBrandProps) => {
21+
const Component = component as any;
22+
return (
23+
<Component className={css(styles.mastheadBrand, className)} tabIndex={0} {...props}>
24+
{children}
25+
</Component>
26+
);
27+
};
28+
MastheadBrand.displayName = 'MastheadBrand';
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
import * as React from 'react';
2+
import styles from '@patternfly/react-styles/css/components/Masthead/masthead';
3+
import { css } from '@patternfly/react-styles';
4+
5+
export interface MastheadContentProps extends React.DetailedHTMLProps<React.HTMLProps<HTMLDivElement>, HTMLDivElement> {
6+
/** Content rendered inside of the masthead content block. */
7+
children?: React.ReactNode;
8+
/** Additional classes added to the masthead content. */
9+
className?: string;
10+
}
11+
12+
export const MastheadContent: React.FunctionComponent<MastheadContentProps> = ({
13+
children,
14+
className,
15+
...props
16+
}: MastheadContentProps) => (
17+
<div className={css(styles.mastheadContent, className)} {...props}>
18+
{children}
19+
</div>
20+
);
21+
MastheadContent.displayName = 'MastheadContent';
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
import * as React from 'react';
2+
import styles from '@patternfly/react-styles/css/components/Masthead/masthead';
3+
import { css } from '@patternfly/react-styles';
4+
5+
export interface MastheadMainProps extends React.DetailedHTMLProps<React.HTMLProps<HTMLDivElement>, HTMLDivElement> {
6+
/** Content rendered inside of the masthead main block. */
7+
children?: React.ReactNode;
8+
/** Additional classes added to the masthead main. */
9+
className?: string;
10+
}
11+
12+
export const MastheadMain: React.FunctionComponent<MastheadMainProps> = ({
13+
children,
14+
className,
15+
...props
16+
}: MastheadMainProps) => (
17+
<div className={css(styles.mastheadMain, className)} {...props}>
18+
{children}
19+
</div>
20+
);
21+
MastheadMain.displayName = 'MastheadMain';
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
import * as React from 'react';
2+
import styles from '@patternfly/react-styles/css/components/Masthead/masthead';
3+
import { css } from '@patternfly/react-styles';
4+
5+
export interface MastheadToggleProps extends React.DetailedHTMLProps<React.HTMLProps<HTMLDivElement>, HTMLDivElement> {
6+
/** Content rendered inside of the masthead toggle. */
7+
children?: React.ReactNode;
8+
/** Additional classes added to the masthead toggle. */
9+
className?: string;
10+
}
11+
12+
export const MastheadToggle: React.FunctionComponent<MastheadToggleProps> = ({
13+
children,
14+
className,
15+
...props
16+
}: MastheadToggleProps) => (
17+
<div className={css(styles.mastheadToggle, className)} {...props}>
18+
{children}
19+
</div>
20+
);
21+
MastheadToggle.displayName = 'MastheadToggle';
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,135 @@
1+
import React from 'react';
2+
import { shallow, mount } from 'enzyme';
3+
import { Masthead, MastheadBrand, MastheadContent, MastheadMain, MastheadToggle } from '../index';
4+
5+
describe('Masthead', () => {
6+
test('verify basic', () => {
7+
const view = shallow(<Masthead>test</Masthead>);
8+
9+
expect(view).toMatchSnapshot();
10+
});
11+
12+
test('verify full structure', () => {
13+
const view = mount(
14+
<Masthead>
15+
<MastheadToggle>Toggle</MastheadToggle>
16+
<MastheadMain>
17+
<MastheadBrand>Logo</MastheadBrand>
18+
</MastheadMain>
19+
<MastheadContent>
20+
<span>Content</span>
21+
</MastheadContent>
22+
</Masthead>
23+
);
24+
25+
expect(view).toMatchSnapshot();
26+
});
27+
28+
test('verify custom class', () => {
29+
const view = mount(<Masthead className="custom-css">test</Masthead>);
30+
31+
expect(view).toMatchSnapshot();
32+
});
33+
34+
test('verify inline display breakpoints', () => {
35+
const view = mount(
36+
<Masthead
37+
display={{ default: 'inline', sm: 'inline', md: 'inline', lg: 'inline', xl: 'inline', '2xl': 'inline' }}
38+
>
39+
test
40+
</Masthead>
41+
);
42+
43+
expect(view).toMatchSnapshot();
44+
});
45+
46+
test('verify stack display breakpoints', () => {
47+
const view = mount(
48+
<Masthead display={{ default: 'stack', sm: 'stack', md: 'stack', lg: 'stack', xl: 'stack', '2xl': 'stack' }}>
49+
test
50+
</Masthead>
51+
);
52+
53+
expect(view).toMatchSnapshot();
54+
});
55+
56+
Object.values(['insetNone', 'insetXs', 'insetSm', 'insetMd', 'insetLg', 'insetXl', 'inset2xl', 'inset3xl'] as [
57+
'insetNone',
58+
'insetXs',
59+
'insetSm',
60+
'insetMd',
61+
'insetLg',
62+
'insetXl',
63+
'inset2xl',
64+
'inset3xl'
65+
]).forEach(inset => {
66+
test(`verify ${inset} inset breakpoints`, () => {
67+
const view = mount(
68+
<Masthead inset={{ default: inset, sm: inset, md: inset, lg: inset, xl: inset, '2xl': inset }}>test</Masthead>
69+
);
70+
expect(view).toMatchSnapshot();
71+
});
72+
});
73+
});
74+
75+
describe('MastheadBrand', () => {
76+
test('verify basic', () => {
77+
const view = shallow(<MastheadBrand>test</MastheadBrand>);
78+
79+
expect(view).toMatchSnapshot();
80+
});
81+
82+
test('verify custom class', () => {
83+
const view = mount(<MastheadBrand className="custom-css">test</MastheadBrand>);
84+
85+
expect(view).toMatchSnapshot();
86+
});
87+
88+
test('verify custom component', () => {
89+
const view = mount(<MastheadBrand component="div">test</MastheadBrand>);
90+
91+
expect(view).toMatchSnapshot();
92+
});
93+
});
94+
95+
describe('MastheadContent', () => {
96+
test('verify basic', () => {
97+
const view = shallow(<MastheadContent>test</MastheadContent>);
98+
99+
expect(view).toMatchSnapshot();
100+
});
101+
102+
test('verify custom class', () => {
103+
const view = mount(<MastheadContent className="custom-css">test</MastheadContent>);
104+
105+
expect(view).toMatchSnapshot();
106+
});
107+
});
108+
109+
describe('MastheadMain', () => {
110+
test('verify basic', () => {
111+
const view = shallow(<MastheadMain>test</MastheadMain>);
112+
113+
expect(view).toMatchSnapshot();
114+
});
115+
116+
test('verify custom class', () => {
117+
const view = mount(<MastheadMain className="custom-css">test</MastheadMain>);
118+
119+
expect(view).toMatchSnapshot();
120+
});
121+
});
122+
123+
describe('MastheadToggle', () => {
124+
test('verify basic', () => {
125+
const view = shallow(<MastheadToggle>test</MastheadToggle>);
126+
127+
expect(view).toMatchSnapshot();
128+
});
129+
130+
test('verify custom class', () => {
131+
const view = mount(<MastheadToggle className="custom-css">test</MastheadToggle>);
132+
133+
expect(view).toMatchSnapshot();
134+
});
135+
});

0 commit comments

Comments
 (0)