-
Notifications
You must be signed in to change notification settings - Fork 377
feat(Dropdown next): Added the next version of the dropdown component #7955
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
Merged
Merged
Changes from all commits
Commits
Show all changes
13 commits
Select commit
Hold shift + click to select a range
8206fc9
feat(Dropdown next): Added the next version of the dropdown component
c198fd8
add aria label to kebab toggle
872ebbc
clean up commented code
354bcee
Fixed keyboard interactions
bcf37fe
some review updates
e15f93b
updates from comments
81b53f3
fixed comment
e10f700
add dropdown group styling
798d715
Add dividers to demo and remove plain menu checkbox
6d56661
update imports, remove comma from list
8b27dd7
fix descriptions and mark toggle required
ee1023c
change prop name
3830fe6
remove old dropdown styles
File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
115 changes: 115 additions & 0 deletions
115
packages/react-core/src/next/components/Dropdown/Dropdown.tsx
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,115 @@ | ||
| import React from 'react'; | ||
| import { css } from '@patternfly/react-styles'; | ||
| import { Menu, MenuContent, MenuProps } from '../../../components/Menu'; | ||
| import { Popper } from '../../../helpers/Popper/Popper'; | ||
|
|
||
| export interface DropdownProps extends MenuProps { | ||
| /** Anything which can be rendered in a dropdown. */ | ||
| children?: React.ReactNode; | ||
| /** Classes applied to root element of dropdown. */ | ||
| className?: string; | ||
| /** Renderer for a custom dropdown toggle. Forwards a ref to the toggle. */ | ||
| toggle: (toggleRef: React.RefObject<any>) => React.ReactNode; | ||
| /** Flag to indicate if menu is opened.*/ | ||
| isOpen?: boolean; | ||
| /** Function callback called when user selects item. */ | ||
| onSelect?: (event?: React.MouseEvent<Element, MouseEvent>, itemId?: string | number) => void; | ||
| /** Callback to allow the dropdown component to change the open state of the menu. | ||
| * Triggered by clicking outside of the menu, or by pressing either tab or escape. */ | ||
| onOpenChange?: (isOpen: boolean) => void; | ||
| /** Indicates if the menu should be without the outer box-shadow. */ | ||
| isPlain?: boolean; | ||
| /** Indicates if the menu should be scrollable. */ | ||
| isScrollable?: boolean; | ||
| /** Min width of the menu. */ | ||
| minWidth?: string; | ||
| } | ||
|
|
||
| export const Dropdown: React.FunctionComponent<DropdownProps> = ({ | ||
| children, | ||
| className, | ||
| onSelect, | ||
| isOpen, | ||
| toggle, | ||
| onOpenChange, | ||
| isPlain, | ||
| isScrollable, | ||
| minWidth, | ||
| ...props | ||
| }: DropdownProps) => { | ||
| const localMenuRef = React.useRef<HTMLDivElement>(); | ||
| const toggleRef = React.useRef<HTMLButtonElement>(); | ||
| const containerRef = React.useRef<HTMLDivElement>(); | ||
|
|
||
| const menuRef = (props.innerRef as React.RefObject<HTMLDivElement>) || localMenuRef; | ||
| React.useEffect(() => { | ||
| const handleMenuKeys = (event: KeyboardEvent) => { | ||
| if (!isOpen && toggleRef.current?.contains(event.target as Node)) { | ||
| // toggle was clicked open, focus on first menu item | ||
| if (event.key === 'Enter') { | ||
| setTimeout(() => { | ||
| const firstElement = menuRef.current.querySelector('li > button:not(:disabled)'); | ||
| firstElement && (firstElement as HTMLElement).focus(); | ||
| }, 0); | ||
| } | ||
| } | ||
| // Close the menu on tab or escape if onOpenChange is provided | ||
| if ( | ||
| (isOpen && onOpenChange && menuRef.current?.contains(event.target as Node)) || | ||
| toggleRef.current?.contains(event.target as Node) | ||
| ) { | ||
| if (event.key === 'Escape' || event.key === 'Tab') { | ||
| onOpenChange(!isOpen); | ||
| toggleRef.current?.focus(); | ||
| } | ||
| } | ||
| }; | ||
|
|
||
| const handleClickOutside = (event: MouseEvent) => { | ||
| // If the event is not on the toggle and onOpenChange callback is provided, close the menu | ||
| if (isOpen && onOpenChange && !toggleRef?.current?.contains(event.target as Node)) { | ||
| if (isOpen && !menuRef.current?.contains(event.target as Node)) { | ||
| onOpenChange(false); | ||
| } | ||
| } | ||
| }; | ||
|
|
||
| window.addEventListener('keydown', handleMenuKeys); | ||
| window.addEventListener('click', handleClickOutside); | ||
|
|
||
| return () => { | ||
| window.removeEventListener('keydown', handleMenuKeys); | ||
| window.removeEventListener('click', handleClickOutside); | ||
| }; | ||
| }, [isOpen, menuRef, onOpenChange]); | ||
|
|
||
| const menu = ( | ||
| <Menu | ||
| className={css(className)} | ||
| ref={menuRef} | ||
| onSelect={(event, itemId) => onSelect(event, itemId)} | ||
| isPlain={isPlain} | ||
| isScrollable={isScrollable} | ||
| {...(minWidth && { | ||
| style: { | ||
| '--pf-c-menu--MinWidth': minWidth | ||
| } as React.CSSProperties | ||
| })} | ||
| {...props} | ||
| > | ||
| <MenuContent>{children}</MenuContent> | ||
| </Menu> | ||
| ); | ||
| return ( | ||
| <div ref={containerRef}> | ||
| <Popper | ||
| trigger={toggle(toggleRef)} | ||
| removeFindDomNode | ||
| popper={menu} | ||
| appendTo={containerRef.current || undefined} | ||
| isVisible={isOpen} | ||
| /> | ||
| </div> | ||
| ); | ||
| }; | ||
| Dropdown.displayName = 'Dropdown'; | ||
25 changes: 25 additions & 0 deletions
25
packages/react-core/src/next/components/Dropdown/DropdownGroup.tsx
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,25 @@ | ||
| import React from 'react'; | ||
| import { css } from '@patternfly/react-styles'; | ||
| import { MenuGroupProps, MenuGroup } from '../../../components/Menu'; | ||
|
|
||
| export interface DropdownGroupProps extends Omit<MenuGroupProps, 'ref'> { | ||
| /** Anything which can be rendered in a dropdown group. */ | ||
| children: React.ReactNode; | ||
| /** Classes applied to root element of dropdown group */ | ||
| className?: string; | ||
| /** Label of the dropdown group */ | ||
| label?: string; | ||
| } | ||
|
|
||
| export const DropdownGroup: React.FunctionComponent<DropdownGroupProps> = ({ | ||
| children, | ||
| className, | ||
| label, | ||
| labelHeadingLevel = 'h1', | ||
| ...props | ||
| }: DropdownGroupProps) => ( | ||
| <MenuGroup className={css(className)} label={label} labelHeadingLevel={labelHeadingLevel} {...props}> | ||
| {children} | ||
| </MenuGroup> | ||
| ); | ||
| DropdownGroup.displayName = 'DropdownGroup'; |
24 changes: 24 additions & 0 deletions
24
packages/react-core/src/next/components/Dropdown/DropdownItem.tsx
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,24 @@ | ||
| import React from 'react'; | ||
| import { css } from '@patternfly/react-styles'; | ||
| import { MenuItemProps, MenuItem } from '../../../components/Menu'; | ||
|
|
||
| export interface DropdownItemProps extends Omit<MenuItemProps, 'ref'> { | ||
| /** Anything which can be rendered in a dropdown item */ | ||
| children?: React.ReactNode; | ||
| /** Classes applied to root element of dropdown item */ | ||
| className?: string; | ||
| /** Description of the dropdown item */ | ||
| description?: React.ReactNode; | ||
| } | ||
|
|
||
| export const DropdownItem: React.FunctionComponent<MenuItemProps> = ({ | ||
| children, | ||
| className, | ||
| description, | ||
| ...props | ||
| }: DropdownItemProps) => ( | ||
| <MenuItem className={css(className)} description={description} {...props}> | ||
| {children} | ||
| </MenuItem> | ||
| ); | ||
| DropdownItem.displayName = 'DropdownItem'; |
21 changes: 21 additions & 0 deletions
21
packages/react-core/src/next/components/Dropdown/DropdownList.tsx
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,21 @@ | ||
| import React from 'react'; | ||
| import { css } from '@patternfly/react-styles'; | ||
| import { MenuListProps, MenuList } from '../../../components/Menu'; | ||
|
|
||
| export interface DropdownListProps extends MenuListProps { | ||
| /** Anything which can be rendered in a dropdown list */ | ||
| children: React.ReactNode; | ||
| /** Classes applied to root element of dropdown list */ | ||
| className?: string; | ||
| } | ||
|
|
||
| export const DropdownList: React.FunctionComponent<MenuListProps> = ({ | ||
| children, | ||
| className, | ||
| ...props | ||
| }: DropdownListProps) => ( | ||
| <MenuList className={css(className)} {...props}> | ||
| {children} | ||
| </MenuList> | ||
| ); | ||
| DropdownList.displayName = 'DropdownList'; |
31 changes: 31 additions & 0 deletions
31
packages/react-core/src/next/components/Dropdown/examples/Dropdown.md
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,31 @@ | ||
| --- | ||
| id: Dropdown | ||
| section: components | ||
| cssPrefix: pf-c-dropdown | ||
| propComponents: ['Dropdown', DropdownGroup, 'DropdownItem', 'DropdownList'] | ||
| beta: true | ||
| --- | ||
|
|
||
| import EllipsisVIcon from '@patternfly/react-icons/dist/esm/icons/ellipsis-v-icon'; | ||
|
|
||
| ## Examples | ||
|
|
||
| ### Basic | ||
|
|
||
| ```ts file="./DropdownBasic.tsx" | ||
| ``` | ||
|
|
||
| ### With kebab toggle | ||
|
|
||
| ```ts file="./DropdownWithKebabToggle.tsx" | ||
| ``` | ||
|
|
||
| ### With groups | ||
|
|
||
| ```ts file="./DropdownWithGroups.tsx" | ||
| ``` | ||
|
|
||
| ### With descriptions | ||
|
|
||
| ```ts file="./DropdownWithDescriptions.tsx" | ||
| ``` |
54 changes: 54 additions & 0 deletions
54
packages/react-core/src/next/components/Dropdown/examples/DropdownBasic.tsx
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,54 @@ | ||
| import React from 'react'; | ||
| import { Dropdown, DropdownItem, DropdownList } from '@patternfly/react-core/next'; | ||
| import { Divider, MenuToggle } from '@patternfly/react-core'; | ||
|
|
||
| export const DropdownBasic: React.FunctionComponent = () => { | ||
| const [isOpen, setIsOpen] = React.useState(false); | ||
| const menuRef = React.useRef<HTMLDivElement>(null); | ||
|
|
||
| const onToggleClick = () => { | ||
| setIsOpen(!isOpen); | ||
| }; | ||
|
|
||
| const onSelect = (_event: React.MouseEvent<Element, MouseEvent> | undefined, itemId: string | number | undefined) => { | ||
| // eslint-disable-next-line no-console | ||
| console.log('selected', itemId); | ||
| setIsOpen(false); | ||
| }; | ||
|
|
||
| return ( | ||
| <Dropdown | ||
| innerRef={menuRef} | ||
| isOpen={isOpen} | ||
| onSelect={onSelect} | ||
| onOpenChange={isOpen => setIsOpen(isOpen)} | ||
| toggle={toggleRef => ( | ||
| <MenuToggle ref={toggleRef} onClick={onToggleClick} isExpanded={isOpen}> | ||
| Dropdown | ||
| </MenuToggle> | ||
| )} | ||
| > | ||
| <DropdownList> | ||
| <DropdownItem itemId={0} key="link"> | ||
| Link | ||
| </DropdownItem> | ||
| <DropdownItem itemId={1} key="action" to="#default-link2" onClick={ev => ev.preventDefault()}> | ||
| Action | ||
| </DropdownItem> | ||
| <DropdownItem itemId={2} isDisabled key="disabled link"> | ||
| Disabled link | ||
| </DropdownItem> | ||
| <DropdownItem itemId={3} isDisabled key="disabled action" to="#default-link4"> | ||
| Disabled action | ||
| </DropdownItem> | ||
| <Divider key="separator" /> | ||
| <DropdownItem itemId={4} key="separated link"> | ||
| Separated link | ||
| </DropdownItem> | ||
| <DropdownItem itemId={5} key="separated action"> | ||
| Separated action | ||
| </DropdownItem> | ||
| </DropdownList> | ||
| </Dropdown> | ||
| ); | ||
| }; |
60 changes: 60 additions & 0 deletions
60
packages/react-core/src/next/components/Dropdown/examples/DropdownWithDescriptions.tsx
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,60 @@ | ||
| import React from 'react'; | ||
| import { Dropdown, DropdownItem, DropdownList } from '@patternfly/react-core/next'; | ||
| import { MenuToggle } from '@patternfly/react-core'; | ||
|
|
||
| export const DropdownWithDescriptions: React.FunctionComponent = () => { | ||
| const [isOpen, setIsOpen] = React.useState(false); | ||
| const menuRef = React.useRef<HTMLDivElement>(null); | ||
|
|
||
| const onToggleClick = () => { | ||
| setIsOpen(!isOpen); | ||
| }; | ||
|
|
||
| const onSelect = (_event: React.MouseEvent<Element, MouseEvent> | undefined, itemId: string | number | undefined) => { | ||
| // eslint-disable-next-line no-console | ||
| console.log('selected', itemId); | ||
| setIsOpen(false); | ||
| }; | ||
|
|
||
| return ( | ||
| <Dropdown | ||
| innerRef={menuRef} | ||
| isOpen={isOpen} | ||
| onSelect={onSelect} | ||
| minWidth="150px" | ||
| onOpenChange={isOpen => setIsOpen(isOpen)} | ||
| toggle={toggleRef => ( | ||
| <MenuToggle ref={toggleRef} isFullWidth onClick={onToggleClick} isExpanded={isOpen}> | ||
| Dropdown | ||
| </MenuToggle> | ||
| )} | ||
| > | ||
| <DropdownList> | ||
| <DropdownItem itemId={0} key="link" description="This is a description"> | ||
| Link | ||
| </DropdownItem> | ||
| <DropdownItem | ||
| itemId={1} | ||
| key="action" | ||
| description="This is a very long description that describes the menu item" | ||
| to="#default-link2" | ||
| onClick={ev => ev.preventDefault()} | ||
| > | ||
| Action | ||
| </DropdownItem> | ||
| <DropdownItem itemId={2} isDisabled description="Disabled link description" key="disabled link"> | ||
| Disabled link | ||
| </DropdownItem> | ||
| <DropdownItem | ||
| itemId={3} | ||
| isDisabled | ||
| description="This is a description" | ||
| key="disabled action" | ||
| to="#default-link4" | ||
| > | ||
| Disabled action | ||
| </DropdownItem> | ||
| </DropdownList> | ||
| </Dropdown> | ||
| ); | ||
| }; |
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
Uh oh!
There was an error while loading. Please reload this page.