-
Notifications
You must be signed in to change notification settings - Fork 376
feat(Compass): add Compass components #12093
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from all commits
818a0c8
4eee4b0
f109114
5d99165
c23197d
7940595
679ddbd
e09a18c
dc532ee
c20eb11
3e38390
83e783f
fedf1bb
05de07b
5cb97e7
098e5ec
083a065
d1c968c
519a195
876dd3d
fc37eb2
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,107 @@ | ||
| import { Drawer, DrawerContent, DrawerProps } from '../Drawer'; | ||
| import styles from '@patternfly/react-styles/css/components/Compass/compass'; | ||
| import { css } from '@patternfly/react-styles'; | ||
|
|
||
| import compassBackgroundImageLight from '@patternfly/react-tokens/dist/esm/c_compass_BackgroundImage_light'; | ||
| import compassBackgroundImageDark from '@patternfly/react-tokens/dist/esm/c_compass_BackgroundImage_dark'; | ||
|
|
||
| export interface CompassProps extends React.HTMLProps<HTMLDivElement> { | ||
| /** Additional classes added to the compass. */ | ||
| className?: string; | ||
| /** Content placed at the top of the layout */ | ||
| header?: React.ReactNode; | ||
| /** Flag indicating if the header is expanded */ | ||
| isHeaderExpanded?: boolean; | ||
| /** Content placed at the horizontal start of the layout, before the main content */ | ||
| sidebarStart?: React.ReactNode; | ||
| /** Flag indicating if the start sidebar is expanded */ | ||
| isSidebarStartExpanded?: boolean; | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Might be missing something, but setting these expanded props to false doesn't do anything in the examples added. It removes the expanded class, but the content is still visible.
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. At least one design file showed these blocks sliding in and out of the layout like drawers, so Katie added support for it via these props & |
||
| /** Content placed at the center of the layout */ | ||
| main?: React.ReactNode; | ||
| /** Content placed at the horizontal end of the layout, after the main content */ | ||
| sidebarEnd?: React.ReactNode; | ||
| /** Flag indicating if the end sidebar is expanded */ | ||
| isSidebarEndExpanded?: boolean; | ||
| /** Content placed at the bottom of the layout */ | ||
| footer?: React.ReactNode; | ||
| /** Flag indicating if the footer is expanded */ | ||
| isFooterExpanded?: boolean; | ||
| /** Content rendered in an optional drawer wrapping the layout */ | ||
| drawerContent?: React.ReactNode; | ||
| /** Additional props passed to the drawer */ | ||
| drawerProps?: DrawerProps; | ||
| /** Light theme background image path of the compass */ | ||
| backgroundSrcLight?: string; | ||
| /** Dark theme background image path of the compass */ | ||
| backgroundSrcDark?: string; | ||
| } | ||
|
|
||
| export const Compass: React.FunctionComponent<CompassProps> = ({ | ||
| className, | ||
| header, | ||
| isHeaderExpanded = true, | ||
| sidebarStart, | ||
| isSidebarStartExpanded = true, | ||
| main, | ||
| sidebarEnd, | ||
| isSidebarEndExpanded = true, | ||
| footer, | ||
| isFooterExpanded = true, | ||
| drawerContent, | ||
| drawerProps, | ||
| backgroundSrcLight, | ||
| backgroundSrcDark, | ||
| ...props | ||
| }) => { | ||
| const hasDrawer = drawerContent !== undefined; | ||
|
|
||
| const backgroundImageStyles: { [key: string]: string } = {}; | ||
| if (backgroundSrcLight) { | ||
| backgroundImageStyles[compassBackgroundImageLight.name] = `url(${backgroundSrcLight})`; | ||
| } | ||
| if (backgroundSrcDark) { | ||
| backgroundImageStyles[compassBackgroundImageDark.name] = `url(${backgroundSrcDark})`; | ||
| } | ||
|
|
||
| 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> | ||
| ); | ||
|
|
||
| if (hasDrawer) { | ||
| return ( | ||
| <Drawer {...drawerProps}> | ||
| <DrawerContent panelContent={drawerContent}>{compassContent}</DrawerContent> | ||
| </Drawer> | ||
| ); | ||
| } | ||
|
|
||
| return compassContent; | ||
| }; | ||
|
|
||
| Compass.displayName = 'Compass'; | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,42 @@ | ||
| import { Drawer, DrawerContent, DrawerProps } from '../Drawer'; | ||
| import styles from '@patternfly/react-styles/css/components/Compass/compass'; | ||
| import { css } from '@patternfly/react-styles'; | ||
|
|
||
| interface CompassContentProps extends React.HTMLProps<HTMLDivElement> { | ||
| /** Content of the main compass area. Typically one or more CompassPanel components. */ | ||
| children: React.ReactNode; | ||
| /** Additional classes added to the CompassContent */ | ||
| className?: string; | ||
| /** Content rendered in an optional drawer wrapping the CompassContent */ | ||
| drawerContent?: React.ReactNode; | ||
| /** Additional props passed to the drawer */ | ||
| drawerProps?: DrawerProps; | ||
| } | ||
|
|
||
| export const CompassContent: React.FunctionComponent<CompassContentProps> = ({ | ||
| children, | ||
| className, | ||
| drawerProps, | ||
| drawerContent, | ||
| ...props | ||
| }) => { | ||
| const hasDrawer = drawerContent !== undefined; | ||
|
|
||
| const compassContent = ( | ||
| <div className={css(styles.compassContent, className)} {...props}> | ||
| {children} | ||
| </div> | ||
| ); | ||
|
|
||
| if (hasDrawer) { | ||
| return ( | ||
| <Drawer {...drawerProps}> | ||
| <DrawerContent panelContent={drawerContent}>{compassContent}</DrawerContent> | ||
| </Drawer> | ||
| ); | ||
| } | ||
|
|
||
| return compassContent; | ||
| }; | ||
|
|
||
| CompassContent.displayName = 'CompassContent'; |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,21 @@ | ||
| import styles from '@patternfly/react-styles/css/components/Compass/compass'; | ||
| import { css } from '@patternfly/react-styles'; | ||
|
|
||
| interface CompassHeaderProps { | ||
| /** Content of the logo area */ | ||
| logo?: React.ReactNode; | ||
| /** Content of the navigation area */ | ||
| nav?: React.ReactNode; | ||
| /** Content of the profile area */ | ||
| profile?: React.ReactNode; | ||
|
Comment on lines
+6
to
+10
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Since these are all optional, we should conditionally render their elements below.
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. wdyt @mcoker? Should the logo/nav/profile wrapping divs be present regardless if content is pass to the respective prop? I can't remember if the wrapping divs would effect the spacing or if the nav was vertically aligned independent of the other two divs (or the logo div at least).
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I think if we want to allow someone to pass
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Would it be worth an issue in the followup epic for tweaking the styles so that all 3 don't have to be rendered for the positioning to work? This isn't a blocker by any means, just thinking if we can reduce empty nodes in the DOM it'd be great.
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. A user can already pass |
||
| } | ||
|
|
||
| export const CompassHeader: React.FunctionComponent<CompassHeaderProps> = ({ logo, nav, profile }) => ( | ||
| <> | ||
| <div className={css(`${styles.compass}__logo`)}>{logo}</div> | ||
| <div className={css(styles.compassNav)}>{nav}</div> | ||
| <div className={css(styles.compassProfile)}>{profile}</div> | ||
| </> | ||
| ); | ||
|
|
||
| CompassHeader.displayName = 'CompassHeader'; | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,87 @@ | ||
| import styles from '@patternfly/react-styles/css/components/Compass/compass'; | ||
| import { css } from '@patternfly/react-styles'; | ||
|
|
||
| import compassHeroBackgroundImageLight from '@patternfly/react-tokens/dist/esm/c_compass__hero_BackgroundImage_light'; | ||
| import compassHeroBackgroundImageDark from '@patternfly/react-tokens/dist/esm/c_compass__hero_BackgroundImage_dark'; | ||
| import compassHeroGradientStop1Light from '@patternfly/react-tokens/dist/esm/c_compass__hero_gradient_stop_1_light'; | ||
| import compassHeroGradientStop2Light from '@patternfly/react-tokens/dist/esm/c_compass__hero_gradient_stop_2_light'; | ||
| import compassHeroGradientStop3Light from '@patternfly/react-tokens/dist/esm/c_compass__hero_gradient_stop_3_light'; | ||
| import compassHeroGradientStop1Dark from '@patternfly/react-tokens/dist/esm/c_compass__hero_gradient_stop_1_dark'; | ||
| import compassHeroGradientStop2Dark from '@patternfly/react-tokens/dist/esm/c_compass__hero_gradient_stop_2_dark'; | ||
| import compassHeroGradientStop3Dark from '@patternfly/react-tokens/dist/esm/c_compass__hero_gradient_stop_3_dark'; | ||
|
|
||
| interface CompassHeroProps extends Omit<React.HTMLProps<HTMLDivElement>, 'content'> { | ||
| /** Content of the hero */ | ||
| children?: React.ReactNode; | ||
| /** Additional classes added to the hero */ | ||
| className?: string; | ||
| /** Light theme background image path of the hero */ | ||
| backgroundSrcLight?: string; | ||
| /** Dark theme background image path of the hero */ | ||
| backgroundSrcDark?: string; | ||
| /** Light theme gradient of the hero */ | ||
| gradientLight?: { | ||
| stop1?: string; | ||
| stop2?: string; | ||
| stop3?: string; | ||
| }; | ||
| /** Dark theme gradient of the hero */ | ||
| gradientDark?: { | ||
| stop1?: string; | ||
| stop2?: string; | ||
| stop3?: string; | ||
| }; | ||
| } | ||
|
|
||
| export const CompassHero: React.FunctionComponent<CompassHeroProps> = ({ | ||
| className, | ||
| children, | ||
| backgroundSrcLight, | ||
| backgroundSrcDark, | ||
| gradientLight, | ||
| gradientDark, | ||
| ...props | ||
| }) => { | ||
| const backgroundImageStyles: { [key: string]: string } = {}; | ||
| if (backgroundSrcLight) { | ||
| backgroundImageStyles[compassHeroBackgroundImageLight.name] = `url(${backgroundSrcLight})`; | ||
| } | ||
| if (backgroundSrcDark) { | ||
| backgroundImageStyles[compassHeroBackgroundImageDark.name] = `url(${backgroundSrcDark})`; | ||
| } | ||
|
|
||
| if (gradientLight) { | ||
| if (gradientLight.stop1) { | ||
| backgroundImageStyles[compassHeroGradientStop1Light.name] = gradientLight.stop1; | ||
| } | ||
| if (gradientLight.stop2) { | ||
| backgroundImageStyles[compassHeroGradientStop2Light.name] = gradientLight.stop2; | ||
| } | ||
| if (gradientLight.stop3) { | ||
| backgroundImageStyles[compassHeroGradientStop3Light.name] = gradientLight.stop3; | ||
| } | ||
| } | ||
| if (gradientDark) { | ||
| if (gradientDark.stop1) { | ||
| backgroundImageStyles[compassHeroGradientStop1Dark.name] = gradientDark.stop1; | ||
| } | ||
| if (gradientDark.stop2) { | ||
| backgroundImageStyles[compassHeroGradientStop2Dark.name] = gradientDark.stop2; | ||
| } | ||
| if (gradientDark.stop3) { | ||
| backgroundImageStyles[compassHeroGradientStop3Dark.name] = gradientDark.stop3; | ||
| } | ||
| } | ||
|
|
||
| return ( | ||
| <div | ||
| className={css(styles.compassPanel, styles.compassHero, className)} | ||
| style={{ ...props.style, ...backgroundImageStyles }} | ||
| {...props} | ||
| > | ||
| <div className={css(styles.compassHeroBody)}>{children}</div> | ||
| </div> | ||
| ); | ||
| }; | ||
|
|
||
| CompassHero.displayName = 'CompassHero'; |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,43 @@ | ||
| import { Flex, FlexItem } from '../../layouts/Flex'; | ||
| import { CompassPanel } from './CompassPanel'; | ||
| import styles from '@patternfly/react-styles/css/components/Compass/compass'; | ||
| import { css } from '@patternfly/react-styles'; | ||
|
|
||
| interface CompassMainHeaderProps extends Omit<React.HTMLProps<HTMLDivElement>, 'title'> { | ||
| /** Additional classes added to the main header */ | ||
| className?: string; | ||
| /** Styled title. If title or toolbar is provided, the children will be ignored. */ | ||
| title?: React.ReactNode; | ||
| /** Styled toolbar. If title or toolbar is provided, the children will be ignored. */ | ||
| toolbar?: React.ReactNode; | ||
| /** Custom main header content. To opt into a default styling, use the title and toolbar props instead. */ | ||
| children?: React.ReactNode; | ||
| } | ||
|
|
||
| export const CompassMainHeader: React.FunctionComponent<CompassMainHeaderProps> = ({ | ||
| className, | ||
| title, | ||
| toolbar, | ||
| children, | ||
| ...props | ||
| }) => { | ||
| const _content = | ||
| title !== undefined || toolbar !== undefined ? ( | ||
| <CompassPanel> | ||
| <Flex alignItems={{ default: 'alignItemsCenter' }}> | ||
| <FlexItem grow={{ default: 'grow' }}>{title}</FlexItem> | ||
| {toolbar && <FlexItem>{toolbar}</FlexItem>} | ||
| </Flex> | ||
| </CompassPanel> | ||
| ) : ( | ||
| children | ||
| ); | ||
|
|
||
| return ( | ||
| <div className={css(`${styles.compass}__main-header`, className)} {...props}> | ||
| {_content} | ||
| </div> | ||
| ); | ||
| }; | ||
|
|
||
| CompassMainHeader.displayName = 'CompassMainHeader'; |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,21 @@ | ||
| import styles from '@patternfly/react-styles/css/components/Compass/compass'; | ||
| import { css } from '@patternfly/react-styles'; | ||
|
|
||
| interface CompassMessageBarProps extends React.HTMLProps<HTMLDivElement> { | ||
| /** Content of the message bar. Typically a @patternfly/chatbot MessageBar component. */ | ||
| children?: React.ReactNode; | ||
| /** Additional classes added to the message bar */ | ||
| className?: string; | ||
| } | ||
|
|
||
| export const CompassMessageBar: React.FunctionComponent<CompassMessageBarProps> = ({ | ||
| children, | ||
| className, | ||
| ...props | ||
| }) => ( | ||
| <div className={css(styles.compassMessageBar, className)} {...props}> | ||
| {children} | ||
| </div> | ||
| ); | ||
|
|
||
| CompassMessageBar.displayName = 'CompassMessageBar'; |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,51 @@ | ||
| import styles from '@patternfly/react-styles/css/components/Compass/compass'; | ||
| import { css } from '@patternfly/react-styles'; | ||
|
|
||
| interface CompassPanelProps extends React.HTMLProps<HTMLDivElement> { | ||
| /** Content of the panel. */ | ||
| children: React.ReactNode; | ||
| /** Additional classes added to the panel. */ | ||
| className?: string; | ||
| /** Indicates the panel should have a pill border radius */ | ||
| isPill?: boolean; | ||
|
Comment on lines
+9
to
+10
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Just thinking of it, but as part of the followup maybe we can go through any shared props amongst all the new compass stuff and make sure the verbiage is the same/similar.
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Opened #12106 |
||
| /** Indicates the panel should expand to fill the available height */ | ||
| isFullHeight?: boolean; | ||
| /** Indicates the panel should scroll its overflow */ | ||
| isScrollable?: boolean; | ||
| /** Indicates the panel should have no border */ | ||
| hasNoBorder?: boolean; | ||
| /** Indicates the panel should have no padding */ | ||
| hasNoPadding?: boolean; | ||
| /** Indicates the panel should have a "thinking" animation */ | ||
| isThinking?: boolean; | ||
| } | ||
|
|
||
| export const CompassPanel: React.FunctionComponent<CompassPanelProps> = ({ | ||
| children, | ||
| className, | ||
| isPill, | ||
| hasNoBorder, | ||
| hasNoPadding, | ||
| isThinking, | ||
| isFullHeight, | ||
| isScrollable, | ||
| ...props | ||
| }) => ( | ||
| <div | ||
| className={css( | ||
| styles.compassPanel, | ||
| isPill && styles.modifiers.pill, | ||
| hasNoBorder && styles.modifiers.noBorder, | ||
| hasNoPadding && styles.modifiers.noPadding, | ||
| isThinking && 'pf-v6-m-thinking', | ||
| isFullHeight && styles.modifiers.fullHeight, | ||
| isScrollable && styles.modifiers.scrollable, | ||
| className | ||
| )} | ||
| {...props} | ||
| > | ||
| {children} | ||
| </div> | ||
| ); | ||
|
|
||
| CompassPanel.displayName = 'CompassPanel'; | ||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
For these "area" props (header, footer, main, side panels), would we want to have wrapper containers introduced (CompassFooter, CompassMain, etc) that would be suggested to be passed in?
Or do we just want people to pass whatever they want into them, using the other Compass components in this PR when necessary?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think we want to leave these wrapper containers to the
Compasssince that controls the actual layout, and allows a user some flexibility if they want the layout but don't want the specific structure imposed by the typical child components. Those components (CompassHeader, CompassMessageBar, CompassContent, etc) are more opinionated/helper components to help achieve the same PoC style.