diff --git a/packages/react-core/src/components/Menu/examples/Menu.md b/packages/react-core/src/components/Menu/examples/Menu.md index 31b87730634..de09eeb021e 100644 --- a/packages/react-core/src/components/Menu/examples/Menu.md +++ b/packages/react-core/src/components/Menu/examples/Menu.md @@ -20,112 +20,12 @@ import AngleLeftIcon from '@patternfly/react-icons/dist/esm/icons/angle-left-ico ### Basic -```js -import React from 'react'; -import { Menu, MenuContent, MenuList, MenuItem, Checkbox } from '@patternfly/react-core'; - -class MenuBasicList extends React.Component { - constructor(props) { - super(props); - this.state = { - activeItem: 0, - isPlain: false - }; - this.onSelect = (event, itemId) => { - console.log(`clicked ${itemId}`); - this.setState({ - activeItem: itemId - }); - }; - this.togglePlain = checked => { - this.setState({ - isPlain: checked - }); - }; - } - - render() { - const { activeItem, isPlain } = this.state; - return ( - - - - - Action - event.preventDefault()} - > - Link - - Disabled action - - Disabled link - - - - -
- -
-
- ); - } -} +```ts file="MenuBasic.tsx" ``` ### With icons -```js -import React from 'react'; -import { Menu, MenuContent, MenuList, MenuItem } from '@patternfly/react-core'; -import CodeBranchIcon from '@patternfly/react-icons/dist/esm/icons/code-branch-icon'; -import LayerGroupIcon from '@patternfly/react-icons/dist/esm/icons/layer-group-icon'; -import CubeIcon from '@patternfly/react-icons/dist/esm/icons/cube-icon'; - -class MenuIconsList extends React.Component { - constructor(props) { - super(props); - this.state = { - activeItem: 0 - }; - this.onSelect = (event, itemId) => { - this.setState({ - activeItem: itemId - }); - }; - } - - render() { - const { activeItem } = this.state; - return ( - - - - } itemId={0}> - From git - - } itemId={1}> - Container image - - } itemId={2}> - Docker file - - - - - ); - } -} +```ts file="MenuWithIcons.tsx" ``` ### With checkbox @@ -135,1258 +35,87 @@ class MenuIconsList extends React.Component { ### Filtering with text input -```js -import React from 'react'; -import { Menu, MenuList, MenuItem, MenuContent, MenuInput, TextInput, Divider } from '@patternfly/react-core'; - -class MenuWithFiltering extends React.Component { - constructor(props) { - super(props); - this.state = { - activeItem: 0, - input: '' - }; - - this.onSelect = (event, itemId) => { - this.setState({ - activeItem: itemId - }); - }; - - this.handleTextInputChange = (value, field) => { - this.setState({ [field]: value }); - }; - } - - render() { - const { activeItem, input } = this.state; - const menuListItemsText = ['Action 1', 'Action 2', 'Action 3']; - - const menuListItems = menuListItemsText - .filter(item => !input || item.toLowerCase().includes(input.toString().toLowerCase())) - .map((currentValue, index) => ( - - {currentValue} - - )); - if (input && menuListItems.length === 0) { - menuListItems.push( - - No results found - - ); - } - - return ( - - - this.handleTextInputChange(value, 'input')} - /> - - - - {menuListItems} - - - ); - } -} +```ts file="MenuFilteringWithTextInput.tsx" ``` ### With links -```js -import React from 'react'; -import { Menu, MenuContent, MenuList, MenuItem } from '@patternfly/react-core'; - -class MenuWithLinks extends React.Component { - constructor(props) { - super(props); - this.state = { - activeItem: 0 - }; - this.onSelect = (event, itemId) => { - this.setState({ - activeItem: itemId - }); - }; - } - - render() { - const { activeItem } = this.state; - return ( - - - - - Link 1 - - - Link 2 - - - Link 3 - - - - - ); - } -} +```ts file="MenuWithLinks.tsx" ``` ### With separator(s) -```js -import React from 'react'; -import { Divider, Menu, MenuContent, MenuList, MenuItem } from '@patternfly/react-core'; - -class MenuWithSeparators extends React.Component { - constructor(props) { - super(props); - this.state = { - activeItem: 0 - }; - this.onSelect = (event, itemId) => { - this.setState({ - activeItem: itemId - }); - }; - } - - render() { - const { activeItem } = this.state; - return ( - - - - Action 1 - Action 2 - - Action 3 - - - - ); - } -} +```ts file="MenuWithSeparators.tsx" ``` ### With titled groups -```js -import React from 'react'; -import { Menu, MenuContent, MenuGroup, MenuList, MenuItem, Divider } from '@patternfly/react-core'; - -class MenuWithTitledGroups extends React.Component { - constructor(props) { - super(props); - this.state = { - activeItem: 0 - }; - this.onSelect = (event, itemId) => { - this.setState({ - activeItem: itemId - }); - }; - } - - render() { - const { activeItem } = this.state; - return ( - - - - - - Link not in group - - - - - - - - Link 1 - - Link 2 - - - - - - - Link 1 - - - Link 2 - - - - - - ); - } -} +```ts file="MenuWithTitledGroups.tsx" ``` ### With description -```js -import React from 'react'; -import { Menu, MenuContent, MenuList, MenuItem } from '@patternfly/react-core'; -import CodeBranchIcon from '@patternfly/react-icons/dist/esm/icons/code-branch-icon'; - -class MenuWithDescription extends React.Component { - constructor(props) { - super(props); - this.state = { - activeItem: 0 - }; - this.onSelect = (event, itemId) => { - this.setState({ - activeItem: itemId - }); - }; - } - - render() { - const { activeItem } = this.state; - return ( - - - - } description="Description" itemId={0}> - Action 1 - - } description="Description" itemId={1}> - Action 2 disabled - - } - description="Nunc non ornare ex, et pretium dui. Duis nec augue at urna elementum blandit tincidunt eget metus. Aenean sed metus id urna dignissim interdum. Aenean vel nisl vitae arcu vehicula pulvinar eget nec turpis. Cras sit amet est est." - itemId={2} - > - Action 3 - - - - - ); - } -} +```ts file="MenuWithDescription.tsx" ``` ### With actions -```js -import React from 'react'; -import { Menu, MenuContent, MenuGroup, MenuList, MenuItem, MenuItemAction } from '@patternfly/react-core'; -import BarsIcon from '@patternfly/react-icons/dist/esm/icons/bars-icon'; -import ClipboardIcon from '@patternfly/react-icons/dist/esm/icons/clipboard-icon'; -import CodeBranchIcon from '@patternfly/react-icons/dist/esm/icons/code-branch-icon'; -import BellIcon from '@patternfly/react-icons/dist/esm/icons/bell-icon'; - -class MenuWithActions extends React.Component { - constructor(props) { - super(props); - this.state = { - activeItem: 0, - isActionMenuOpen: false, - selectedItems: [0, 2, 3] - }; - - this.onSelect = (event, itemId) => { - if (this.state.selectedItems.indexOf(itemId) !== -1) { - this.setState({ - selectedItems: this.state.selectedItems.filter(id => id !== itemId) - }); - } else { - this.setState({ - selectedItems: [...this.state.selectedItems, itemId] - }); - } - }; - } - - render() { - const { activeItem, selectedItems } = this.state; - - return ( - console.log(`clicked on ${itemId} - ${actionId}`)} - activeItemId={activeItem} - > - - - - } - actionId="code" - onClick={() => console.log('clicked on code icon')} - aria-label="Code" - /> - } - description="This is a description" - itemId={0} - > - Item 1 - - } actionId="alert" aria-label="Alert" />} - description="This is a description" - itemId={1} - > - Item 2 - - } actionId="copy" aria-label="Copy" />} - itemId={2} - > - Item 3 - - } actionId="expand" aria-label="Expand" />} - description="This is a description" - itemId={3} - > - Item 4 - - - - - - ); - } -} +```ts file="MenuWithActions.tsx" ``` ### With favorites -```js -import React from 'react'; -import { Menu, MenuContent, MenuItem, MenuItemAction, MenuGroup, MenuList, Divider } from '@patternfly/react-core'; -import BarsIcon from '@patternfly/react-icons/dist/esm/icons/bars-icon'; -import ClipboardIcon from '@patternfly/react-icons/dist/esm/icons/clipboard-icon'; -import BellIcon from '@patternfly/react-icons/dist/esm/icons/bell-icon'; - -class MenuWithFavorites extends React.Component { - constructor(props) { - super(props); - this.state = { - activeItem: 0, - favorites: [] - }; - - this.onSelect = (event, itemId) => { - this.setState({ - activeItem: itemId - }); - }; - this.onFavorite = (event, itemId, actionId) => { - console.log(`clicked ${itemId} - ${actionId}`); - if (actionId === 'fav') { - const isFavorite = this.state.favorites.includes(itemId); - if (isFavorite) { - this.setState({ - favorites: this.state.favorites.filter(fav => fav !== itemId) - }); - } else { - this.setState({ - favorites: [...this.state.favorites, itemId] - }); - } - } - }; - } - - render() { - const { activeItem, favorites } = this.state; - - const items = [ - { - text: 'Item 1', - description: 'Description 1', - itemId: 'item-1', - action: , - actionId: 'bars' - }, - { - text: 'Item 2', - description: 'Description 2', - itemId: 'item-2', - action: , - actionId: 'clipboard' - }, - { - text: 'Item 3', - description: 'Description 3', - itemId: 'item-3', - action: , - actionId: 'bell' - } - ]; - - return ( - - - {favorites.length > 0 && ( - - - - {items - // map the items into the favorites group that have been favorited - .filter(item => favorites.includes(item.itemId)) - .map(item => { - const { text, description, itemId, action, actionId } = item; - return ( - } - > - {text} - - ); - })} - - - - - )} - - - {items.map(item => { - const { text, description, itemId, action, actionId } = item; - const isFavorited = favorites.includes(item.itemId); - return ( - } - > - {text} - - ); - })} - - - - - ); - } -} +```ts file="MenuWithFavorites.tsx" ``` ### Option single select -```js -import React from 'react'; -import { Menu, MenuContent, MenuList, MenuItem } from '@patternfly/react-core'; -import TableIcon from '@patternfly/react-icons/dist/esm/icons/table-icon'; - -class MenuOptionSingleSelect extends React.Component { - constructor(props) { - super(props); - this.state = { - activeItem: 0, - selectedItem: 0 - }; - - this.onSelect = (event, itemId) => { - this.setState({ - activeItem: itemId, - selectedItem: itemId - }); - }; - } - - render() { - const { activeItem, selectedItem } = this.state; - return ( - - - - Option 1 - Option 2 - } itemId={2}> - Option 3 - - - - - ); - } -} +```ts file="MenuOptionSingleSelect.tsx" ``` ### Option multi select -```js -import React from 'react'; -import { Menu, MenuContent, MenuList, MenuItem } from '@patternfly/react-core'; -import TableIcon from '@patternfly/react-icons/dist/esm/icons/table-icon'; - -class MenuOptionMultiSelect extends React.Component { - constructor(props) { - super(props); - this.state = { - activeItem: 0, - selectedItems: [] - }; - this.onSelect = (event, itemId) => { - if (this.state.selectedItems.indexOf(itemId) !== -1) { - this.setState({ - selectedItems: this.state.selectedItems.filter(id => id !== itemId) - }); - } else { - this.setState({ - selectedItems: [...this.state.selectedItems, itemId] - }); - } - }; - } - - render() { - const { activeItem, selectedItems } = this.state; - return ( - - - - Option 1 - Option 2 - } itemId={2}> - Option 3 - - - - - ); - } -} +```ts file="MenuOptionMultiSelect.tsx" ``` ### With drilldown -```ts file="./MenuDrilldown.tsx" isBeta +```ts file="./MenuWithDrilldown.tsx" isBeta ``` ### With drilldown - initial drill in state To render an initially drilled in menu, the `menuDrilledIn`, `drilldownPath`, and `activeMenu` states must be set to an initial state. The `menuHeights` state must also be set, defining the height of the root menu. The `setHeight` function passed into the `onGetMenuHeight` property must also account for updating heights, other than the root menu, as menus drill in and out of view. -```ts file="./MenuDrilldownInitialState.tsx" isBeta +```ts file="./MenuWithDrilldownInitialState.tsx" isBeta ``` ### With drilldown - submenu functions -```ts file="./MenuDrilldownSubmenuFunctions.tsx" isBeta +```ts file="./MenuWithDrilldownSubmenuFunctions.tsx" isBeta ``` ### With drilldown breadcrumbs -```js isBeta -import React from 'react'; -import { - Menu, - MenuContent, - MenuList, - MenuItem, - Divider, - DrilldownMenu, - Breadcrumb, - BreadcrumbItem, - BreadcrumbHeading, - MenuBreadcrumb, - Dropdown, - DropdownItem, - BadgeToggle, - Checkbox -} from '@patternfly/react-core'; -import StorageDomainIcon from '@patternfly/react-icons/dist/esm/icons/storage-domain-icon'; -import CodeBranchIcon from '@patternfly/react-icons/dist/esm/icons/code-branch-icon'; -import LayerGroupIcon from '@patternfly/react-icons/dist/esm/icons/layer-group-icon'; -import AngleLeftIcon from '@patternfly/react-icons/dist/esm/icons/angle-left-icon'; -import CubeIcon from '@patternfly/react-icons/dist/esm/icons/cube-icon'; - -class MenuWithDrilldownBreadcrumbs extends React.Component { - constructor(props) { - super(props); - this.state = { - menuDrilledIn: [], - drilldownPath: [], - menuHeights: {}, - activeMenu: 'breadcrumbs-rootMenu', - breadcrumb: undefined, - withMaxMenuHeight: false - }; - - this.onToggle = (isOpen, key) => { - switch (key) { - case 'app': - this.setState({ - breadcrumb: this.appGroupingBreadcrumb(isOpen) - }); - break; - case 'label': - this.setState({ - breadcrumb: this.labelsBreadcrumb(isOpen) - }); - break; - default: - break; - } - }; - - this.onToggleMaxMenuHeight = checked => { - this.setState({ - withMaxMenuHeight: checked - }); - }; - - this.drillOut = (toMenuId, fromPathId, breadcrumb) => { - const indexOfMenuId = this.state.menuDrilledIn.indexOf(toMenuId); - const menuDrilledInSansLast = this.state.menuDrilledIn.slice(0, indexOfMenuId); - const indexOfMenuIdPath = this.state.drilldownPath.indexOf(fromPathId); - const pathSansLast = this.state.drilldownPath.slice(0, indexOfMenuIdPath); - this.setState({ - menuDrilledIn: menuDrilledInSansLast, - drilldownPath: pathSansLast, - activeMenu: toMenuId, - breadcrumb - }); - }; - this.setHeight = (menuId, height) => { - if (!this.state.menuHeights[menuId]) { - this.setState({ - menuHeights: { - ...this.state.menuHeights, - [menuId]: height - } - }); - } - }; - this.drillIn = (fromMenuId, toMenuId, pathId) => { - this.setState({ - menuDrilledIn: [...this.state.menuDrilledIn, fromMenuId], - drilldownPath: [...this.state.drilldownPath, pathId], - activeMenu: toMenuId - }); - }; - - this.startRolloutBreadcrumb = ( - - this.drillOut('breadcrumbs-rootMenu', 'group:start_rollout', null)}> - Root - - Start rollout - - ); - - this.appGroupingBreadcrumb = isOpen => { - return ( - - this.drillOut('breadcrumbs-rootMenu', 'group:start_rollout', null)}> - Root - - - this.onToggle(open, 'app')}> - 1 - - } - isOpen={isOpen} - dropdownItems={[ - } - onClick={() => - this.drillOut('breadcrumbs-drilldownMenuStart', 'group:app_grouping_start', this.startRolloutBreadcrumb) - } - > - Start rollout - - ]} - /> - - Application Grouping - - ); - }; - - this.labelsBreadcrumb = isOpen => ( - - this.drillOut('breadcrumbs-rootMenu', 'group:start_rollout', null)}> - Root - - - this.onToggle(open, 'label')}> - 1 - - } - isOpen={isOpen} - dropdownItems={[ - } - onClick={() => this.drillOut('breadcrumbs-drilldownMenuStart', 'group:labels_start', this.startRolloutBreadcrumb)} - > - Start rollout - - ]} - /> - - Labels - - ); - - this.pauseRolloutsBreadcrumb = ( - - this.drillOut('breadcrumbs-rootMenu', 'group:pause_rollout', null)}> - Root - - Pause rollouts - - ); - - this.pauseRolloutsAppGrpBreadcrumb = ( - - this.drillOut('breadcrumbs-rootMenu', 'group:pause_rollout', null)}> - Root - - this.drillOut('breadcrumbs-drilldownMenuPause', 'group:app_grouping', this.pauseRolloutsBreadcrumb)} - > - Pause rollouts - - Application Grouping - - ); - - this.pauseRolloutsLabelsBreadcrumb = ( - - this.drillOut('breadcrumbs-rootMenu', 'group:pause_rollout', null)}> - Root - - this.drillOut('breadcrumbs-drilldownMenuPause', 'group:labels', this.pauseRolloutsBreadcrumb)} - > - Pause rollouts - - Labels - - ); - - this.addStorageBreadcrumb = ( - - this.drillOut('breadcrumbs-rootMenu', 'group:storage', null)}> - Root - - Add storage - - ); - } - - render() { - const { menuDrilledIn, drilldownPath, activeMenu, menuHeights, breadcrumb, withMaxMenuHeight } = this.state; - - return ( - <> - -
- - {breadcrumb && ( - <> - {breadcrumb} - - - )} - - - this.setState({ breadcrumb: this.startRolloutBreadcrumb })} - drilldownMenu={ - - this.setState({ breadcrumb: this.appGroupingBreadcrumb(false) })} - drilldownMenu={ - - Group A - Group B - Group C - Group D - Group E - Group F - Group G - - } - > - Application grouping - - Count - this.setState({ breadcrumb: this.labelsBreadcrumb(false) })} - drilldownMenu={ - - Label 1 - Label 2 - Label 3 - - } - > - Labels - - Annotations - - } - > - Start rollout - - this.setState({ breadcrumb: this.pauseRolloutsBreadcrumb })} - drilldownMenu={ - - this.setState({ breadcrumb: this.pauseRolloutsAppGrpBreadcrumb })} - drilldownMenu={ - - Group A - Group B - Group C - - } - > - Application grouping - - Count - this.setState({ breadcrumb: this.pauseRolloutsLabelsBreadcrumb })} - drilldownMenu={ - - Label 1 - Label 2 - Label 3 - - } - > - Labels - - Annotations - - } - > - Pause rollouts - - } - direction="down" - onClick={() => this.setState({ breadcrumb: this.addStorageBreadcrumb })} - drilldownMenu={ - - } itemId="git"> - From git - - } itemId="container"> - Container image - - } itemId="docker"> - Docker file - - - } - > - Add storage - - Edit - Delete deployment config - - - - - ); - } -} +```ts file="MenuWithDrilldownBreadcrumbs.tsx" isBeta ``` ### Scrollable -```js -import React from 'react'; -import { Menu, MenuContent, MenuList, MenuItem } from '@patternfly/react-core'; - -class MenuBasicList extends React.Component { - constructor(props) { - super(props); - this.state = { - activeItem: 0 - }; - this.onSelect = (event, itemId) => { - console.log(`clicked ${itemId}`); - this.setState({ - activeItem: itemId - }); - }; - } - - render() { - const { activeItem } = this.state; - return ( - - - - Action 1 - Action 2 - Action 3 - Action 4 - Action 5 - Action 6 - Action 7 - Action 8 - Action 9 - Action 10 - Action 11 - Action 12 - Action 13 - Action 14 - Action 15 - event.preventDefault()} - > - Link - - Disabled action - - Disabled link - - - - - ); - } -} +```ts file="MenuScrollable.tsx" ``` ### Scrollable with custom menu height -```js -import React from 'react'; -import { Menu, MenuContent, MenuList, MenuItem } from '@patternfly/react-core'; - -class MenuBasicList extends React.Component { - constructor(props) { - super(props); - this.state = { - activeItem: 0 - }; - this.onSelect = (event, itemId) => { - console.log(`clicked ${itemId}`); - this.setState({ - activeItem: itemId - }); - }; - } - - render() { - const { activeItem } = this.state; - return ( - - - - Action 1 - Action 2 - Action 3 - Action 4 - Action 5 - Action 6 - Action 7 - Action 8 - Action 9 - Action 10 - Action 11 - Action 12 - Action 13 - Action 14 - Action 15 - event.preventDefault()} - > - Link - - Disabled action - - Disabled link - - - - - ); - } -} +```ts file="MenuScrollableCustomMenuHeight.tsx" ``` ### With footer -```js -import React from 'react'; -import { Menu, MenuList, MenuItem, MenuContent, MenuFooter, Button } from '@patternfly/react-core'; - -class FooterMenu extends React.Component { - constructor(props) { - super(props); - this.state = { - activeItem: 0 - }; - this.onSelect = (event, itemId) => { - console.log(`clicked ${itemId}`); - this.setState({ - activeItem: itemId - }); - }; - } - - render() { - const { activeItem } = this.state; - return ( - - - - Action - event.preventDefault()} - > - Link - - Disabled action - - Disabled link - - - - - - - - ); - } -} +```ts file="MenuWithFooter.tsx" ``` ### With view more -```js -import React from 'react'; -import { Menu, MenuList, MenuItem, MenuContent, MenuFooter, Spinner } from '@patternfly/react-core'; - -class ViewMoreMenu extends React.Component { - constructor(props) { - super(props); - this.state = { - activeItem: 0, - isLoading: false, - numOptions: 3 - }; - this.activeItemRef = React.createRef(); - this.viewMoreRef = React.createRef(); - - this.menuOptions = [ - - Action - , - event.preventDefault()} - > - Link - , - - Disabled action - , - - Disabled link - , - - Action 2 - , - - Action 3 - , - - Action 4 - , - - Action 5 - , - - Final option - - ]; - - this.onSelect = (event, itemId) => { - console.log(`clicked ${itemId}`); - this.setState({ - activeItem: itemId - }); - }; - - this.simulateNetworkCall = networkCallback => { - setTimeout(networkCallback, 2000); - }; - - this.getNextValidItem = (startingIndex, maxLength) => { - let validItem; - for (let i = startingIndex; i < maxLength; i++) { - if (this.menuOptions[i].props.isDisabled) { - continue; - } else { - validItem = this.menuOptions[i]; - break; - } - } - return validItem; - }; - - this.loadMoreOptions = stateCallback => { - const newLength = - this.state.numOptions + 3 <= this.menuOptions.length ? this.state.numOptions + 3 : this.menuOptions.length; - const prevPosition = this.state.numOptions; - const nextValidItem = this.getNextValidItem(prevPosition, newLength); - - this.setState({ numOptions: newLength, isLoading: false, activeItem: nextValidItem.props.itemId }, stateCallback); - }; - - this.onViewMoreClick = event => { - this.setState({ isLoading: true }); - this.simulateNetworkCall(() => { - this.loadMoreOptions(() => { - this.activeItemRef.current.focus(); - }); - }); - }; - - this.onViewMoreKeyDown = event => { - if (!(event.key === ' ' || event.key === 'Enter')) { - return; - } - event.stopPropagation(); - event.preventDefault(); - - this.setState({ isLoading: true }); - this.simulateNetworkCall(() => { - this.loadMoreOptions(() => { - if (this.viewMoreRef.current) { - this.viewMoreRef.current.tabIndex = -1; - } - const firstMenuItem = this.activeItemRef.current.closest('ul').firstChild; - firstMenuItem.querySelector('button, a').tabIndex = -1; - this.activeItemRef.current.tabIndex = 0; - this.activeItemRef.current.focus(); - }); - }); - }; - } - - render() { - const { activeItem, isLoading, numOptions } = this.state; - return ( - - - - {this.menuOptions.slice(0, numOptions).map(option => { - const props = option.props; - - return ( - - ); - })} - {numOptions !== this.menuOptions.length && ( - - {isLoading ? : 'View more'} - - )} - - - - ); - } -} +```ts file="MenuWithViewMore.tsx" ``` diff --git a/packages/react-core/src/components/Menu/examples/MenuBasic.tsx b/packages/react-core/src/components/Menu/examples/MenuBasic.tsx new file mode 100644 index 00000000000..98280db25ab --- /dev/null +++ b/packages/react-core/src/components/Menu/examples/MenuBasic.tsx @@ -0,0 +1,51 @@ +import React from 'react'; +import { Menu, MenuContent, MenuList, MenuItem, Checkbox } from '@patternfly/react-core'; + +export const MenuBasic: React.FunctionComponent = () => { + const [activeItem, setActiveItem] = React.useState(0); + const [isPlain, setIsPlain] = React.useState(false); + + const onSelect = (_event: React.MouseEvent | undefined, itemId: number | string | undefined) => { + const item = itemId as number; // eslint-disable-next-line no-console + console.log(`clicked ${itemId}`); + setActiveItem(item); + }; + + const togglePlain = (checked: boolean) => { + setIsPlain(checked); + }; + + return ( + + + + + Action + event.preventDefault()} + > + Link + + Disabled action + + Disabled link + + + + +
+ +
+
+ ); +}; diff --git a/packages/react-core/src/components/Menu/examples/MenuFilteringWithTextInput.tsx b/packages/react-core/src/components/Menu/examples/MenuFilteringWithTextInput.tsx new file mode 100644 index 00000000000..4e8f32e2453 --- /dev/null +++ b/packages/react-core/src/components/Menu/examples/MenuFilteringWithTextInput.tsx @@ -0,0 +1,52 @@ +import React from 'react'; +import { Menu, MenuList, MenuItem, MenuContent, MenuInput, TextInput, Divider } from '@patternfly/react-core'; + +export const MenuFilteringWithTextInput: React.FunctionComponent = () => { + const [activeItem, setActiveItem] = React.useState(0); + const [input, setInput] = React.useState(''); + + const onSelect = (_event: React.MouseEvent | undefined, itemId: number | string | undefined) => { + const item = itemId as number; // eslint-disable-next-line no-console + console.log(`clicked ${itemId}`); + setActiveItem(item); + }; + + const handleTextInputChange = (value: string) => { + setInput(value); + }; + + const menuListItemsText = ['Action 1', 'Action 2', 'Action 3']; + + const menuListItems = menuListItemsText + .filter(item => !input || item.toLowerCase().includes(input.toString().toLowerCase())) + .map((currentValue, index) => ( + + {currentValue} + + )); + if (input && menuListItems.length === 0) { + menuListItems.push( + + No results found + + ); + } + + return ( + + + handleTextInputChange(value)} + /> + + + + {menuListItems} + + + ); +}; diff --git a/packages/react-core/src/components/Menu/examples/MenuOptionMultiSelect.tsx b/packages/react-core/src/components/Menu/examples/MenuOptionMultiSelect.tsx new file mode 100644 index 00000000000..d8c333699e6 --- /dev/null +++ b/packages/react-core/src/components/Menu/examples/MenuOptionMultiSelect.tsx @@ -0,0 +1,30 @@ +import React from 'react'; +import { Menu, MenuContent, MenuList, MenuItem } from '@patternfly/react-core'; +import TableIcon from '@patternfly/react-icons/dist/esm/icons/table-icon'; + +export const MenuOptionMultiSelect: React.FunctionComponent = () => { + const [selectedItems, setSelectedItems] = React.useState([]); + + const onSelect = (_event: React.MouseEvent | undefined, itemId: number | string | undefined) => { + const item = itemId as number; + if (selectedItems.indexOf(item) !== -1) { + setSelectedItems(selectedItems.filter(id => id !== item)); + } else { + setSelectedItems([...selectedItems, item]); + } + }; + + return ( + + + + Option 1 + Option 2 + } itemId={2}> + Option 3 + + + + + ); +}; diff --git a/packages/react-core/src/components/Menu/examples/MenuOptionSingleSelect.tsx b/packages/react-core/src/components/Menu/examples/MenuOptionSingleSelect.tsx new file mode 100644 index 00000000000..57e21bcbd99 --- /dev/null +++ b/packages/react-core/src/components/Menu/examples/MenuOptionSingleSelect.tsx @@ -0,0 +1,27 @@ +import React from 'react'; +import { Menu, MenuContent, MenuList, MenuItem } from '@patternfly/react-core'; +import TableIcon from '@patternfly/react-icons/dist/esm/icons/table-icon'; + +export const MenuOptionSingleSelect: React.FunctionComponent = () => { + const [activeItem, setActiveItem] = React.useState(0); + const [selectedItem, setSelectedItem] = React.useState(0); + + const onSelect = (_event, itemId) => { + setActiveItem(itemId); + setSelectedItem(itemId); + }; + + return ( + + + + Option 1 + Option 2 + } itemId={2}> + Option 3 + + + + + ); +}; diff --git a/packages/react-core/src/components/Menu/examples/MenuScrollable.tsx b/packages/react-core/src/components/Menu/examples/MenuScrollable.tsx new file mode 100644 index 00000000000..e4909ec1dfc --- /dev/null +++ b/packages/react-core/src/components/Menu/examples/MenuScrollable.tsx @@ -0,0 +1,48 @@ +import React from 'react'; +import { Menu, MenuContent, MenuList, MenuItem } from '@patternfly/react-core'; + +export const MenuScrollable: React.FunctionComponent = () => { + const [activeItem, setActiveItem] = React.useState(0); + + const onSelect = (_event, itemId) => { + // eslint-disable-next-line no-console + console.log(`clicked ${itemId}`); + setActiveItem(itemId); + }; + + return ( + + + + Action 1 + Action 2 + Action 3 + Action 4 + Action 5 + Action 6 + Action 7 + Action 8 + Action 9 + Action 10 + Action 11 + Action 12 + Action 13 + Action 14 + Action 15 + event.preventDefault()} + > + Link + + Disabled action + + Disabled link + + + + + ); +}; diff --git a/packages/react-core/src/components/Menu/examples/MenuScrollableCustomMenuHeight.tsx b/packages/react-core/src/components/Menu/examples/MenuScrollableCustomMenuHeight.tsx new file mode 100644 index 00000000000..f1891efae60 --- /dev/null +++ b/packages/react-core/src/components/Menu/examples/MenuScrollableCustomMenuHeight.tsx @@ -0,0 +1,48 @@ +import React from 'react'; +import { Menu, MenuContent, MenuList, MenuItem } from '@patternfly/react-core'; + +export const MenuScrollableCustomMenuHeight: React.FunctionComponent = () => { + const [activeItem, setActiveItem] = React.useState(0); + + const onSelect = (_event, itemId) => { + // eslint-disable-next-line no-console + console.log(`clicked ${itemId}`); + setActiveItem(itemId); + }; + + return ( + + + + Action 1 + Action 2 + Action 3 + Action 4 + Action 5 + Action 6 + Action 7 + Action 8 + Action 9 + Action 10 + Action 11 + Action 12 + Action 13 + Action 14 + Action 15 + event.preventDefault()} + > + Link + + Disabled action + + Disabled link + + + + + ); +}; diff --git a/packages/react-core/src/components/Menu/examples/MenuWithActions.tsx b/packages/react-core/src/components/Menu/examples/MenuWithActions.tsx new file mode 100644 index 00000000000..13ff5017510 --- /dev/null +++ b/packages/react-core/src/components/Menu/examples/MenuWithActions.tsx @@ -0,0 +1,75 @@ +import React from 'react'; +import { Menu, MenuContent, MenuGroup, MenuList, MenuItem, MenuItemAction } from '@patternfly/react-core'; +import BarsIcon from '@patternfly/react-icons/dist/esm/icons/bars-icon'; +import ClipboardIcon from '@patternfly/react-icons/dist/esm/icons/clipboard-icon'; +import CodeBranchIcon from '@patternfly/react-icons/dist/esm/icons/code-branch-icon'; +import BellIcon from '@patternfly/react-icons/dist/esm/icons/bell-icon'; + +export const MenuWithActions: React.FunctionComponent = () => { + const [selectedItems, setSelectedItems] = React.useState([0, 2, 3]); + + const onSelect = (_event: React.MouseEvent | undefined, itemId: number | string | undefined) => { + const item = itemId as number; + if (selectedItems.indexOf(item) !== -1) { + setSelectedItems(selectedItems.filter(id => id !== item)); + } else { + setSelectedItems([...selectedItems, item]); + } + }; + + return ( + console.log(`clicked on ${itemId} - ${actionId}`)} + activeItemId={0} + > + + + + } + actionId="code" + // eslint-disable-next-line no-console + onClick={() => console.log('clicked on code icon')} + aria-label="Code" + /> + } + description="This is a description" + itemId={0} + > + Item 1 + + } actionId="alert" aria-label="Alert" />} + description="This is a description" + itemId={1} + > + Item 2 + + } actionId="copy" aria-label="Copy" />} + itemId={2} + > + Item 3 + + } actionId="expand" aria-label="Expand" />} + description="This is a description" + itemId={3} + > + Item 4 + + + + + + ); +}; diff --git a/packages/react-core/src/components/Menu/examples/MenuWithCheckbox.tsx b/packages/react-core/src/components/Menu/examples/MenuWithCheckbox.tsx index 2640d18fb7c..0980d539388 100644 --- a/packages/react-core/src/components/Menu/examples/MenuWithCheckbox.tsx +++ b/packages/react-core/src/components/Menu/examples/MenuWithCheckbox.tsx @@ -1,11 +1,11 @@ import React from 'react'; import { Menu, MenuContent, MenuList, MenuItem } from '@patternfly/react-core'; -export const MenuCheckboxList: React.FunctionComponent = () => { +export const MenuWithCheckbox: React.FunctionComponent = () => { const [selectedItems, setSelectedItems] = React.useState([]); /* eslint no-unused-vars: ["error", {"args": "after-used"}] */ - const onSelect = (event: React.MouseEvent, itemId: number | string) => { + const onSelect = (_event: React.MouseEvent | undefined, itemId: number | string | undefined) => { const item = itemId as number; if (selectedItems.includes(item)) { setSelectedItems(selectedItems.filter(id => id !== item)); diff --git a/packages/react-core/src/components/Menu/examples/MenuWithDescription.tsx b/packages/react-core/src/components/Menu/examples/MenuWithDescription.tsx new file mode 100644 index 00000000000..f6b3cc77ea5 --- /dev/null +++ b/packages/react-core/src/components/Menu/examples/MenuWithDescription.tsx @@ -0,0 +1,35 @@ +import React from 'react'; +import { Menu, MenuContent, MenuList, MenuItem } from '@patternfly/react-core'; +import CodeBranchIcon from '@patternfly/react-icons/dist/esm/icons/code-branch-icon'; + +export const MenuWithDescription: React.FunctionComponent = () => { + const [activeItem, setActiveItem] = React.useState(0); + + const onSelect = (_event: React.MouseEvent | undefined, itemId: number | string | undefined) => { + const item = itemId as number; // eslint-disable-next-line no-console + console.log(`clicked ${item}`); + setActiveItem(item); + }; + + return ( + + + + } description="Description" itemId={0}> + Action 1 + + } description="Description" itemId={1}> + Action 2 disabled + + } + description="Nunc non ornare ex, et pretium dui. Duis nec augue at urna elementum blandit tincidunt eget metus. Aenean sed metus id urna dignissim interdum. Aenean vel nisl vitae arcu vehicula pulvinar eget nec turpis. Cras sit amet est est." + itemId={2} + > + Action 3 + + + + + ); +}; diff --git a/packages/react-core/src/components/Menu/examples/MenuDrilldown.tsx b/packages/react-core/src/components/Menu/examples/MenuWithDrilldown.tsx similarity index 99% rename from packages/react-core/src/components/Menu/examples/MenuDrilldown.tsx rename to packages/react-core/src/components/Menu/examples/MenuWithDrilldown.tsx index 6798b35f8c9..0918400a12a 100644 --- a/packages/react-core/src/components/Menu/examples/MenuDrilldown.tsx +++ b/packages/react-core/src/components/Menu/examples/MenuWithDrilldown.tsx @@ -5,7 +5,7 @@ import CodeBranchIcon from '@patternfly/react-icons/dist/esm/icons/code-branch-i import LayerGroupIcon from '@patternfly/react-icons/dist/esm/icons/layer-group-icon'; import CubeIcon from '@patternfly/react-icons/dist/esm/icons/cube-icon'; -export const MenuDrilldown: React.FunctionComponent = () => { +export const MenuWithDrilldown: React.FunctionComponent = () => { const [menuDrilledIn, setMenuDrilledIn] = React.useState([]); const [drilldownPath, setDrilldownPath] = React.useState([]); const [menuHeights, setMenuHeights] = React.useState({}); diff --git a/packages/react-core/src/components/Menu/examples/MenuWithDrilldownBreadcrumbs.tsx b/packages/react-core/src/components/Menu/examples/MenuWithDrilldownBreadcrumbs.tsx new file mode 100644 index 00000000000..61ed5fc80c4 --- /dev/null +++ b/packages/react-core/src/components/Menu/examples/MenuWithDrilldownBreadcrumbs.tsx @@ -0,0 +1,331 @@ +import React from 'react'; +import { + Menu, + MenuContent, + MenuList, + MenuItem, + Divider, + DrilldownMenu, + Breadcrumb, + BreadcrumbItem, + BreadcrumbHeading, + MenuBreadcrumb, + Dropdown, + DropdownItem, + BadgeToggle, + Checkbox +} from '@patternfly/react-core'; +import StorageDomainIcon from '@patternfly/react-icons/dist/esm/icons/storage-domain-icon'; +import CodeBranchIcon from '@patternfly/react-icons/dist/esm/icons/code-branch-icon'; +import LayerGroupIcon from '@patternfly/react-icons/dist/esm/icons/layer-group-icon'; +import AngleLeftIcon from '@patternfly/react-icons/dist/esm/icons/angle-left-icon'; +import CubeIcon from '@patternfly/react-icons/dist/esm/icons/cube-icon'; + +export const MenuWithDrilldownBreadcrumbs: React.FunctionComponent = () => { + const [menuDrilledIn, setMenuDrilledIn] = React.useState([]); + const [drilldownPath, setDrilldownPath] = React.useState([]); + const [menuHeights, setMenuHeights] = React.useState({}); + const [activeMenu, setActiveMenu] = React.useState('breadcrumbs-rootMenu'); + const [breadcrumb, setBreadcrumb] = React.useState(); + const [withMaxMenuHeight, setWithMaxMenuHeight] = React.useState(false); + + const onToggle = (isOpen: boolean, key: string) => { + switch (key) { + case 'app': + setBreadcrumb(appGroupingBreadcrumb(isOpen)); + break; + case 'label': + setBreadcrumb(labelsBreadcrumb(isOpen)); + break; + default: + break; + } + }; + + const onToggleMaxMenuHeight = (checked: boolean) => { + setWithMaxMenuHeight(checked); + }; + + const drillOut = (toMenuId: string, fromPathId: string, _breadcrumb: JSX.Element | null) => { + const indexOfMenuId = menuDrilledIn.indexOf(toMenuId); + const menuDrilledInSansLast = menuDrilledIn.slice(0, indexOfMenuId); + const indexOfMenuIdPath = drilldownPath.indexOf(fromPathId); + const pathSansLast = drilldownPath.slice(0, indexOfMenuIdPath); + setMenuDrilledIn(menuDrilledInSansLast); + setDrilldownPath(pathSansLast); + setActiveMenu(toMenuId); + }; + const setHeight = (menuId: string, height: number) => { + if (!menuHeights[menuId]) { + setMenuHeights({ ...menuHeights, [menuId]: height }); + } + }; + const drillIn = (fromMenuId: string, toMenuId: string, pathId: string) => { + setMenuDrilledIn([...menuDrilledIn, fromMenuId]); + setDrilldownPath([...drilldownPath, pathId]); + setActiveMenu(toMenuId); + }; + + const startRolloutBreadcrumb = ( + + drillOut('breadcrumbs-rootMenu', 'group:start_rollout', null)}> + Root + + Start rollout + + ); + + const appGroupingBreadcrumb = (isOpen: boolean) => ( + + drillOut('breadcrumbs-rootMenu', 'group:start_rollout', null)}> + Root + + + onToggle(open, 'app')}> + 1 + + } + isOpen={isOpen} + dropdownItems={[ + } + onClick={() => + drillOut('breadcrumbs-drilldownMenuStart', 'group:app_grouping_start', startRolloutBreadcrumb) + } + > + Start rollout + + ]} + /> + + Application Grouping + + ); + + const labelsBreadcrumb = (isOpen: boolean) => ( + + drillOut('breadcrumbs-rootMenu', 'group:start_rollout', null)}> + Root + + + onToggle(open, 'label')}> + 1 + + } + isOpen={isOpen} + dropdownItems={[ + } + onClick={() => drillOut('breadcrumbs-drilldownMenuStart', 'group:labels_start', startRolloutBreadcrumb)} + > + Start rollout + + ]} + /> + + Labels + + ); + + const pauseRolloutsBreadcrumb = ( + + drillOut('breadcrumbs-rootMenu', 'group:pause_rollout', null)}> + Root + + Pause rollouts + + ); + + const pauseRolloutsAppGrpBreadcrumb = ( + + drillOut('breadcrumbs-rootMenu', 'group:pause_rollout', null)}> + Root + + drillOut('breadcrumbs-drilldownMenuPause', 'group:app_grouping', pauseRolloutsBreadcrumb)} + > + Pause rollouts + + Application Grouping + + ); + + const pauseRolloutsLabelsBreadcrumb = ( + + drillOut('breadcrumbs-rootMenu', 'group:pause_rollout', null)}> + Root + + drillOut('breadcrumbs-drilldownMenuPause', 'group:labels', pauseRolloutsBreadcrumb)} + > + Pause rollouts + + Labels + + ); + + const addStorageBreadcrumb = ( + + drillOut('breadcrumbs-rootMenu', 'group:storage', null)}> + Root + + Add storage + + ); + + return ( + <> + +
+ drillOut} + onGetMenuHeight={setHeight} + > + {breadcrumb && ( + <> + {breadcrumb} + + + )} + + + setBreadcrumb(startRolloutBreadcrumb)} + drilldownMenu={ + + setBreadcrumb(appGroupingBreadcrumb(false))} + drilldownMenu={ + + Group A + Group B + Group C + Group D + Group E + Group F + Group G + + } + > + Application grouping + + Count + setBreadcrumb(labelsBreadcrumb(false))} + drilldownMenu={ + + Label 1 + Label 2 + Label 3 + + } + > + Labels + + Annotations + + } + > + Start rollout + + setBreadcrumb(pauseRolloutsBreadcrumb)} + drilldownMenu={ + + setBreadcrumb(pauseRolloutsAppGrpBreadcrumb)} + drilldownMenu={ + + Group A + Group B + Group C + + } + > + Application grouping + + Count + setBreadcrumb(pauseRolloutsLabelsBreadcrumb)} + drilldownMenu={ + + Label 1 + Label 2 + Label 3 + + } + > + Labels + + Annotations + + } + > + Pause rollouts + + } + direction="down" + onClick={() => setBreadcrumb(addStorageBreadcrumb)} + drilldownMenu={ + + } itemId="git"> + From git + + } itemId="container"> + Container image + + } itemId="docker"> + Docker file + + + } + > + Add storage + + Edit + Delete deployment config + + + + + ); +}; diff --git a/packages/react-core/src/components/Menu/examples/MenuDrilldownInitialState.tsx b/packages/react-core/src/components/Menu/examples/MenuWithDrilldownInitialState.tsx similarity index 100% rename from packages/react-core/src/components/Menu/examples/MenuDrilldownInitialState.tsx rename to packages/react-core/src/components/Menu/examples/MenuWithDrilldownInitialState.tsx diff --git a/packages/react-core/src/components/Menu/examples/MenuDrilldownSubmenuFunctions.tsx b/packages/react-core/src/components/Menu/examples/MenuWithDrilldownSubmenuFunctions.tsx similarity index 98% rename from packages/react-core/src/components/Menu/examples/MenuDrilldownSubmenuFunctions.tsx rename to packages/react-core/src/components/Menu/examples/MenuWithDrilldownSubmenuFunctions.tsx index 5568ee484dc..9629b890d2a 100644 --- a/packages/react-core/src/components/Menu/examples/MenuDrilldownSubmenuFunctions.tsx +++ b/packages/react-core/src/components/Menu/examples/MenuWithDrilldownSubmenuFunctions.tsx @@ -5,7 +5,7 @@ import CodeBranchIcon from '@patternfly/react-icons/dist/esm/icons/code-branch-i import LayerGroupIcon from '@patternfly/react-icons/dist/esm/icons/layer-group-icon'; import CubeIcon from '@patternfly/react-icons/dist/esm/icons/cube-icon'; -export const MenuDrilldownSubmenuFunctions: React.FunctionComponent = () => { +export const MenuWithDrilldownSubmenuFunctions: React.FunctionComponent = () => { const [menuDrilledIn, setMenuDrilledIn] = React.useState([]); const [drilldownPath, setDrilldownPath] = React.useState([]); const [menuHeights, setMenuHeights] = React.useState({}); diff --git a/packages/react-core/src/components/Menu/examples/MenuWithFavorites.tsx b/packages/react-core/src/components/Menu/examples/MenuWithFavorites.tsx new file mode 100644 index 00000000000..2e778d95533 --- /dev/null +++ b/packages/react-core/src/components/Menu/examples/MenuWithFavorites.tsx @@ -0,0 +1,104 @@ +import React from 'react'; +import { Menu, MenuContent, MenuItem, MenuItemAction, MenuGroup, MenuList, Divider } from '@patternfly/react-core'; +import BarsIcon from '@patternfly/react-icons/dist/esm/icons/bars-icon'; +import ClipboardIcon from '@patternfly/react-icons/dist/esm/icons/clipboard-icon'; +import BellIcon from '@patternfly/react-icons/dist/esm/icons/bell-icon'; + +export const MenuWithFavorites: React.FunctionComponent = () => { + const [activeItem, setActiveItem] = React.useState(0); + const [favorites, setFavorites] = React.useState([]); + + const onSelect = (_event: React.MouseEvent | undefined, itemId: number | string | undefined) => { + const item = itemId as number; // eslint-disable-next-line no-console + console.log(`clicked ${item}`); + setActiveItem(item); + }; + + const onFavorite = (event, itemId: string, actionId: string) => { + // eslint-disable-next-line no-console + console.log(`clicked ${itemId} - ${actionId}`); + if (actionId === 'fav') { + const isFavorite = favorites.includes(itemId); + if (isFavorite) { + setFavorites(favorites.filter(fav => fav !== itemId)); + } else { + setFavorites([...favorites, itemId]); + } + } + }; + + const items = [ + { + text: 'Item 1', + description: 'Description 1', + itemId: 'item-1', + action: , + actionId: 'bars' + }, + { + text: 'Item 2', + description: 'Description 2', + itemId: 'item-2', + action: , + actionId: 'clipboard' + }, + { + text: 'Item 3', + description: 'Description 3', + itemId: 'item-3', + action: , + actionId: 'bell' + } + ]; + + return ( + + + {favorites.length > 0 && ( + + + + {items + .filter(item => favorites.includes(item.itemId)) + .map(item => { + const { text, description, itemId, action, actionId } = item; + return ( + } + > + {text} + + ); + })} + + + + + )} + + + {items.map(item => { + const { text, description, itemId, action, actionId } = item; + const isFavorited = favorites.includes(item.itemId); + return ( + } + > + {text} + + ); + })} + + + + + ); +}; diff --git a/packages/react-core/src/components/Menu/examples/MenuWithFooter.tsx b/packages/react-core/src/components/Menu/examples/MenuWithFooter.tsx new file mode 100644 index 00000000000..eb6f09f059b --- /dev/null +++ b/packages/react-core/src/components/Menu/examples/MenuWithFooter.tsx @@ -0,0 +1,40 @@ +import React from 'react'; +import { Menu, MenuList, MenuItem, MenuContent, MenuFooter, Button } from '@patternfly/react-core'; + +export const MenuWithFooter: React.FunctionComponent = () => { + const [activeItem, setActiveItem] = React.useState(0); + + const onSelect = (_event: React.MouseEvent | undefined, itemId: number | string | undefined) => { + const item = itemId as number; + // eslint-disable-next-line no-console + console.log(`clicked ${item}`); + setActiveItem(item); + }; + + return ( + + + + Action + event.preventDefault()} + > + Link + + Disabled action + + Disabled link + + + + + + + + ); +}; diff --git a/packages/react-core/src/components/Menu/examples/MenuWithIcons.tsx b/packages/react-core/src/components/Menu/examples/MenuWithIcons.tsx new file mode 100644 index 00000000000..d8b65ceb977 --- /dev/null +++ b/packages/react-core/src/components/Menu/examples/MenuWithIcons.tsx @@ -0,0 +1,33 @@ +import React from 'react'; +import { Menu, MenuContent, MenuList, MenuItem } from '@patternfly/react-core'; +import CodeBranchIcon from '@patternfly/react-icons/dist/esm/icons/code-branch-icon'; +import LayerGroupIcon from '@patternfly/react-icons/dist/esm/icons/layer-group-icon'; +import CubeIcon from '@patternfly/react-icons/dist/esm/icons/cube-icon'; + +export const MenuWithIcons: React.FunctionComponent = () => { + const [activeItem, setActiveItem] = React.useState(0); + + const onSelect = (_event: React.MouseEvent | undefined, itemId: number | string | undefined) => { + const item = itemId as number; // eslint-disable-next-line no-console + console.log(`clicked ${item}`); + setActiveItem(item); + }; + + return ( + + + + } itemId={0}> + From git + + } itemId={1}> + Container image + + } itemId={2}> + Docker file + + + + + ); +}; diff --git a/packages/react-core/src/components/Menu/examples/MenuWithLinks.tsx b/packages/react-core/src/components/Menu/examples/MenuWithLinks.tsx new file mode 100644 index 00000000000..07d4fc5eb0f --- /dev/null +++ b/packages/react-core/src/components/Menu/examples/MenuWithLinks.tsx @@ -0,0 +1,30 @@ +import React from 'react'; +import { Menu, MenuContent, MenuList, MenuItem } from '@patternfly/react-core'; + +export const MenuWithLinks: React.FunctionComponent = () => { + const [activeItem, setActiveItem] = React.useState(0); + + const onSelect = (_event: React.MouseEvent | undefined, itemId: number | string | undefined) => { + const item = itemId as number; // eslint-disable-next-line no-console + console.log(`clicked ${item}`); + setActiveItem(item); + }; + + return ( + + + + + Link 1 + + + Link 2 + + + Link 3 + + + + + ); +}; diff --git a/packages/react-core/src/components/Menu/examples/MenuWithSeparators.tsx b/packages/react-core/src/components/Menu/examples/MenuWithSeparators.tsx new file mode 100644 index 00000000000..6c1980def14 --- /dev/null +++ b/packages/react-core/src/components/Menu/examples/MenuWithSeparators.tsx @@ -0,0 +1,26 @@ +import React from 'react'; +import { Divider, Menu, MenuContent, MenuList, MenuItem } from '@patternfly/react-core'; + +export const MenuWithSeparators: React.FunctionComponent = () => { + const [activeItem, setActiveItem] = React.useState(0); + + const onSelect = (_event: React.MouseEvent | undefined, itemId: number | string | undefined) => { + const item = itemId as number; + // eslint-disable-next-line no-console + console.log(`clicked ${item}`); + setActiveItem(item); + }; + + return ( + + + + Action 1 + Action 2 + + Action 3 + + + + ); +}; diff --git a/packages/react-core/src/components/Menu/examples/MenuWithTitledGroups.tsx b/packages/react-core/src/components/Menu/examples/MenuWithTitledGroups.tsx new file mode 100644 index 00000000000..6318fbaaf04 --- /dev/null +++ b/packages/react-core/src/components/Menu/examples/MenuWithTitledGroups.tsx @@ -0,0 +1,46 @@ +import React from 'react'; +import { Menu, MenuContent, MenuGroup, MenuList, MenuItem, Divider } from '@patternfly/react-core'; + +export const MenuWithTitledGroups: React.FunctionComponent = () => { + const [activeItem, setActiveItem] = React.useState(0); + + const onSelect = (_event: React.MouseEvent | undefined, itemId: number | string | undefined) => { + const item = itemId as number; // eslint-disable-next-line no-console + console.log(`clicked ${item}`); + setActiveItem(item); + }; + + return ( + + + + + + Link not in group + + + + + + + + Link 1 + + Link 2 + + + + + + + Link 1 + + + Link 2 + + + + + + ); +}; diff --git a/packages/react-core/src/components/Menu/examples/MenuWithViewMore.tsx b/packages/react-core/src/components/Menu/examples/MenuWithViewMore.tsx new file mode 100644 index 00000000000..9d84e8b56de --- /dev/null +++ b/packages/react-core/src/components/Menu/examples/MenuWithViewMore.tsx @@ -0,0 +1,118 @@ +import React from 'react'; +import { Menu, MenuList, MenuItem, MenuContent, Spinner } from '@patternfly/react-core'; + +export const MenuWithViewMore: React.FunctionComponent = () => { + const [activeItem, setActiveItem] = React.useState(0); + const [isLoading, setIsLoading] = React.useState(false); + const [numOptions, setNumOptions] = React.useState(3); + // eslint-disable-next-line @typescript-eslint/no-unused-vars + const [menuOptions, setMenuOptions] = React.useState([ + + Action + , + event.preventDefault()} + > + Link + , + + Disabled action + , + + Disabled link + , + + Action 2 + , + + Action 3 + , + + Action 4 + , + + Action 5 + , + + Final option + + ]); + const [visibleOptions, setVisibleOptions] = React.useState(menuOptions.slice(0, numOptions)); + + const activeItemRef = React.useRef(null); + const viewMoreRef = React.useRef(null); + + React.useEffect(() => { + activeItemRef.current?.focus(); + }, [visibleOptions]); + + const onSelect = (_event: React.MouseEvent | undefined, itemId: number | string | undefined) => { + // eslint-disable-next-line no-console + console.log(`clicked ${itemId}`); + setActiveItem(itemId as string); + }; + + const simulateNetworkCall = (networkCallback: () => void) => { + setTimeout(networkCallback, 2000); + }; + + const getNextValidItem = (startingIndex: number, maxLength: number) => { + let validItem; + for (let i = startingIndex; i < maxLength; i++) { + if (menuOptions[i].props.isDisabled) { + continue; + } else { + validItem = menuOptions[i]; + break; + } + } + return validItem; + }; + + const loadMoreOptions = () => { + const newLength = numOptions + 3 <= menuOptions.length ? numOptions + 3 : menuOptions.length; + const prevPosition = numOptions; + const nextValidItem = getNextValidItem(prevPosition, newLength); + + setNumOptions(newLength); + setIsLoading(false); + setActiveItem(nextValidItem.props.itemId); + setVisibleOptions(menuOptions.slice(0, newLength)); + }; + + const onViewMoreClick = () => { + setIsLoading(true); + simulateNetworkCall(() => { + loadMoreOptions(); + }); + }; + + return ( + + + + {visibleOptions.map(option => { + const props = option.props; + + return ; + })} + {numOptions !== menuOptions.length && ( + + {isLoading ? : 'View more'} + + )} + + + + ); +}; diff --git a/packages/react-core/tsconfig.json b/packages/react-core/tsconfig.json index 15945ac6cfa..f3062d0890f 100644 --- a/packages/react-core/tsconfig.json +++ b/packages/react-core/tsconfig.json @@ -1,6 +1,7 @@ { "extends": "../tsconfig.base.json", "compilerOptions": { + "jsx": "react", "rootDir": "./src", "outDir": "./dist/esm", "tsBuildInfoFile": "dist/esm.tsbuildinfo"