diff --git a/components/doc/common/apidoc/index.json b/components/doc/common/apidoc/index.json index 0bd38900b5..b498bdbe84 100644 --- a/components/doc/common/apidoc/index.json +++ b/components/doc/common/apidoc/index.json @@ -32919,6 +32919,14 @@ "type": "ReactNode", "default": "", "description": "Used to get the child elements of the component." + }, + { + "name": "pt", + "optional": true, + "readonly": false, + "type": "TieredMenuPassThroughOptions", + "default": "", + "description": "Uses to pass attributes to DOM elements inside the component." } ] }, @@ -32954,6 +32962,142 @@ ] } } + }, + "interfaces": { + "description": "Defines the custom interfaces used by the module.", + "values": { + "TieredMenuPassThroughMethodOptions": { + "description": "Custom passthrough(pt) option method.", + "relatedProp": "", + "props": [ + { + "name": "props", + "optional": false, + "readonly": false, + "type": "TieredMenuProps" + }, + { + "name": "state", + "optional": false, + "readonly": false, + "type": "TieredMenuState" + }, + { + "name": "context", + "optional": false, + "readonly": false, + "type": "TieredMenuContext" + } + ], + "callbacks": [] + }, + "TieredMenuPassThroughOptions": { + "description": "Custom passthrough(pt) options.", + "relatedProp": "pt", + "props": [ + { + "name": "root", + "optional": true, + "readonly": false, + "type": "TieredMenuPassThroughType>", + "description": "Uses to pass attributes to the root's DOM element." + }, + { + "name": "menu", + "optional": true, + "readonly": false, + "type": "TieredMenuPassThroughType>", + "description": "Uses to pass attributes to the list's DOM element." + }, + { + "name": "menuitem", + "optional": true, + "readonly": false, + "type": "TieredMenuPassThroughType>", + "description": "Uses to pass attributes to the list item's DOM element." + }, + { + "name": "action", + "optional": true, + "readonly": false, + "type": "TieredMenuPassThroughType>", + "description": "Uses to pass attributes to the action's DOM element." + }, + { + "name": "icon", + "optional": true, + "readonly": false, + "type": "TieredMenuPassThroughType | SVGProps>", + "description": "Uses to pass attributes to the icon's DOM element." + }, + { + "name": "label", + "optional": true, + "readonly": false, + "type": "TieredMenuPassThroughType>", + "description": "Uses to pass attributes to the label's DOM element." + }, + { + "name": "submenuIcon", + "optional": true, + "readonly": false, + "type": "TieredMenuPassThroughType | SVGProps>", + "description": "Uses to pass attributes to the submenu icon's DOM element." + }, + { + "name": "separator", + "optional": true, + "readonly": false, + "type": "TieredMenuPassThroughType>", + "description": "Uses to pass attributes to the separator's DOM element." + } + ], + "callbacks": [] + }, + "TieredMenuState": { + "description": "Defines current inline state in TieredMenu component.", + "relatedProp": "", + "props": [ + { + "name": "attributeSelector", + "optional": false, + "readonly": false, + "type": "string", + "description": "Current attributeSelector visible state as a string." + }, + { + "name": "visible", + "optional": false, + "readonly": false, + "type": "boolean", + "description": "Current visible state as a boolean." + } + ], + "callbacks": [] + }, + "TieredMenuContext": { + "description": "Defines current options in TieredMenu component.", + "relatedProp": "", + "props": [ + { + "name": "active", + "optional": false, + "readonly": false, + "type": "boolean", + "description": "Current active state of menuitem as a boolean." + } + ], + "callbacks": [] + } + } + }, + "types": { + "description": "Defines the custom types used by the module.", + "values": { + "TieredMenuPassThroughType": { + "values": "PassThroughType" + } + } } }, "timeline": { diff --git a/components/doc/tieredmenu/pt/ptdoc.js b/components/doc/tieredmenu/pt/ptdoc.js new file mode 100644 index 0000000000..97fffec89e --- /dev/null +++ b/components/doc/tieredmenu/pt/ptdoc.js @@ -0,0 +1,451 @@ +import { TieredMenu } from '../../../lib/tieredmenu/TieredMenu'; +import { DocSectionCode } from '../../common/docsectioncode'; +import { DocSectionText } from '../../common/docsectiontext'; + +export function PTDoc(props) { + const items = [ + { + label: 'File', + icon: 'pi pi-fw pi-file', + items: [ + { + label: 'New', + icon: 'pi pi-fw pi-plus', + items: [ + { + label: 'Bookmark', + icon: 'pi pi-fw pi-bookmark' + }, + { + label: 'Video', + icon: 'pi pi-fw pi-video' + } + ] + }, + { + label: 'Delete', + icon: 'pi pi-fw pi-trash' + }, + { + separator: true + }, + { + label: 'Export', + icon: 'pi pi-fw pi-external-link' + } + ] + }, + { + label: 'Edit', + icon: 'pi pi-fw pi-pencil', + items: [ + { + label: 'Left', + icon: 'pi pi-fw pi-align-left' + }, + { + label: 'Right', + icon: 'pi pi-fw pi-align-right' + }, + { + label: 'Center', + icon: 'pi pi-fw pi-align-center' + }, + { + label: 'Justify', + icon: 'pi pi-fw pi-align-justify' + } + ] + }, + { + label: 'Users', + icon: 'pi pi-fw pi-user', + items: [ + { + label: 'New', + icon: 'pi pi-fw pi-user-plus' + }, + { + label: 'Delete', + icon: 'pi pi-fw pi-user-minus' + }, + { + label: 'Search', + icon: 'pi pi-fw pi-users', + items: [ + { + label: 'Filter', + icon: 'pi pi-fw pi-filter', + items: [ + { + label: 'Print', + icon: 'pi pi-fw pi-print' + } + ] + }, + { + icon: 'pi pi-fw pi-bars', + label: 'List' + } + ] + } + ] + }, + { + label: 'Events', + icon: 'pi pi-fw pi-calendar', + items: [ + { + label: 'Edit', + icon: 'pi pi-fw pi-pencil', + items: [ + { + label: 'Save', + icon: 'pi pi-fw pi-calendar-plus' + }, + { + label: 'Delete', + icon: 'pi pi-fw pi-calendar-minus' + } + ] + }, + { + label: 'Archive', + icon: 'pi pi-fw pi-calendar-times', + items: [ + { + label: 'Remove', + icon: 'pi pi-fw pi-calendar-minus' + } + ] + } + ] + }, + { + separator: true + }, + { + label: 'Quit', + icon: 'pi pi-fw pi-power-off' + } + ]; + + const code = { + basic: ` + ({ className: context.active ? 'bg-primary-200' : undefined }) + }} +/> + `, + javascript: ` +import React from 'react'; +import { TieredMenu } from 'primereact/tieredmenu'; + +export default function PTDemo() { + const items = [ + { + label: 'File', + icon: 'pi pi-fw pi-file', + items: [ + { + label: 'New', + icon: 'pi pi-fw pi-plus', + items: [ + { + label: 'Bookmark', + icon: 'pi pi-fw pi-bookmark' + }, + { + label: 'Video', + icon: 'pi pi-fw pi-video' + } + ] + }, + { + label: 'Delete', + icon: 'pi pi-fw pi-trash' + }, + { + separator: true + }, + { + label: 'Export', + icon: 'pi pi-fw pi-external-link' + } + ] + }, + { + label: 'Edit', + icon: 'pi pi-fw pi-pencil', + items: [ + { + label: 'Left', + icon: 'pi pi-fw pi-align-left' + }, + { + label: 'Right', + icon: 'pi pi-fw pi-align-right' + }, + { + label: 'Center', + icon: 'pi pi-fw pi-align-center' + }, + { + label: 'Justify', + icon: 'pi pi-fw pi-align-justify' + } + ] + }, + { + label: 'Users', + icon: 'pi pi-fw pi-user', + items: [ + { + label: 'New', + icon: 'pi pi-fw pi-user-plus' + }, + { + label: 'Delete', + icon: 'pi pi-fw pi-user-minus' + }, + { + label: 'Search', + icon: 'pi pi-fw pi-users', + items: [ + { + label: 'Filter', + icon: 'pi pi-fw pi-filter', + items: [ + { + label: 'Print', + icon: 'pi pi-fw pi-print' + } + ] + }, + { + icon: 'pi pi-fw pi-bars', + label: 'List' + } + ] + } + ] + }, + { + label: 'Events', + icon: 'pi pi-fw pi-calendar', + items: [ + { + label: 'Edit', + icon: 'pi pi-fw pi-pencil', + items: [ + { + label: 'Save', + icon: 'pi pi-fw pi-calendar-plus' + }, + { + label: 'Delete', + icon: 'pi pi-fw pi-calendar-minus' + } + ] + }, + { + label: 'Archive', + icon: 'pi pi-fw pi-calendar-times', + items: [ + { + label: 'Remove', + icon: 'pi pi-fw pi-calendar-minus' + } + ] + } + ] + }, + { + separator: true + }, + { + label: 'Quit', + icon: 'pi pi-fw pi-power-off' + } + ]; + + return ( +
+ ({ className: context.active ? 'bg-primary-200' : undefined }) + }} + /> +
+ ) +} + `, + typescript: ` +import React from 'react'; +import { TieredMenu } from 'primereact/tieredmenu'; +import { MenuItem } from 'primereact/menuitem'; + +export default function PTDemo() { + const items: MenuItem[] = [ + { + label: 'File', + icon: 'pi pi-fw pi-file', + items: [ + { + label: 'New', + icon: 'pi pi-fw pi-plus', + items: [ + { + label: 'Bookmark', + icon: 'pi pi-fw pi-bookmark' + }, + { + label: 'Video', + icon: 'pi pi-fw pi-video' + } + ] + }, + { + label: 'Delete', + icon: 'pi pi-fw pi-trash' + }, + { + separator: true + }, + { + label: 'Export', + icon: 'pi pi-fw pi-external-link' + } + ] + }, + { + label: 'Edit', + icon: 'pi pi-fw pi-pencil', + items: [ + { + label: 'Left', + icon: 'pi pi-fw pi-align-left' + }, + { + label: 'Right', + icon: 'pi pi-fw pi-align-right' + }, + { + label: 'Center', + icon: 'pi pi-fw pi-align-center' + }, + { + label: 'Justify', + icon: 'pi pi-fw pi-align-justify' + } + ] + }, + { + label: 'Users', + icon: 'pi pi-fw pi-user', + items: [ + { + label: 'New', + icon: 'pi pi-fw pi-user-plus' + }, + { + label: 'Delete', + icon: 'pi pi-fw pi-user-minus' + }, + { + label: 'Search', + icon: 'pi pi-fw pi-users', + items: [ + { + label: 'Filter', + icon: 'pi pi-fw pi-filter', + items: [ + { + label: 'Print', + icon: 'pi pi-fw pi-print' + } + ] + }, + { + icon: 'pi pi-fw pi-bars', + label: 'List' + } + ] + } + ] + }, + { + label: 'Events', + icon: 'pi pi-fw pi-calendar', + items: [ + { + label: 'Edit', + icon: 'pi pi-fw pi-pencil', + items: [ + { + label: 'Save', + icon: 'pi pi-fw pi-calendar-plus' + }, + { + label: 'Delete', + icon: 'pi pi-fw pi-calendar-minus' + } + ] + }, + { + label: 'Archive', + icon: 'pi pi-fw pi-calendar-times', + items: [ + { + label: 'Remove', + icon: 'pi pi-fw pi-calendar-minus' + } + ] + } + ] + }, + { + separator: true + }, + { + label: 'Quit', + icon: 'pi pi-fw pi-power-off' + } + ]; + + return ( +
+ ({ className: context.active ? 'bg-primary-200' : undefined }) + }} + /> +
+ ) +} + ` + }; + + return ( + <> + +
+ ({ className: context.active ? 'bg-primary-200' : undefined }) + }} + /> +
+ + + ); +} diff --git a/components/doc/tieredmenu/pt/wireframe.js b/components/doc/tieredmenu/pt/wireframe.js new file mode 100644 index 0000000000..58b39bc4d2 --- /dev/null +++ b/components/doc/tieredmenu/pt/wireframe.js @@ -0,0 +1,13 @@ +import React from 'react'; +import { DocSectionText } from '../../common/docsectiontext'; + +export const Wireframe = (props) => { + return ( + <> + +
+ tieredmenu +
+ + ); +}; diff --git a/components/lib/tieredmenu/TieredMenu.js b/components/lib/tieredmenu/TieredMenu.js index fadffdf459..664900c375 100644 --- a/components/lib/tieredmenu/TieredMenu.js +++ b/components/lib/tieredmenu/TieredMenu.js @@ -4,7 +4,7 @@ import { CSSTransition } from '../csstransition/CSSTransition'; import { useMatchMedia, useMountEffect, useOverlayListener, useUnmountEffect, useUpdateEffect } from '../hooks/Hooks'; import { OverlayService } from '../overlayservice/OverlayService'; import { Portal } from '../portal/Portal'; -import { classNames, DomHandler, UniqueComponentId, ZIndexUtils } from '../utils/Utils'; +import { classNames, DomHandler, UniqueComponentId, ZIndexUtils, mergeProps } from '../utils/Utils'; import { TieredMenuBase } from './TieredMenuBase'; import { TieredMenuSub } from './TieredMenuSub'; @@ -14,6 +14,13 @@ export const TieredMenu = React.memo( const [visibleState, setVisibleState] = React.useState(!props.popup); const [attributeSelectorState, setAttributeSelectorState] = React.useState(null); + const { ptm } = TieredMenuBase.setMetaData({ + props, + state: { + visible: visibleState, + attributeSelector: attributeSelectorState + } + }); const menuRef = React.useRef(null); const targetRef = React.useRef(null); const styleElementRef = React.useRef(null); @@ -174,6 +181,18 @@ export const TieredMenu = React.memo( props.className ); + const rootProps = mergeProps( + { + ref: menuRef, + id: props.id, + className, + style: props.style, + onClick: onPanelClick + }, + TieredMenuBase.getOtherProps(props), + ptm('root') + ); + return ( -
- +
+
); diff --git a/components/lib/tieredmenu/TieredMenuBase.js b/components/lib/tieredmenu/TieredMenuBase.js index 44f331d484..3616148207 100644 --- a/components/lib/tieredmenu/TieredMenuBase.js +++ b/components/lib/tieredmenu/TieredMenuBase.js @@ -1,6 +1,6 @@ -import { ObjectUtils } from '../utils/Utils'; +import { ComponentBase } from '../componentbase/ComponentBase'; -export const TieredMenuBase = { +export const TieredMenuBase = ComponentBase.extend({ defaultProps: { __TYPE: 'TieredMenu', id: null, @@ -18,7 +18,5 @@ export const TieredMenuBase = { onHide: null, submenuIcon: null, children: undefined - }, - getProps: (props) => ObjectUtils.getMergedProps(props, TieredMenuBase.defaultProps), - getOtherProps: (props) => ObjectUtils.getDiffProps(props, TieredMenuBase.defaultProps) -}; + } +}); diff --git a/components/lib/tieredmenu/TieredMenuSub.js b/components/lib/tieredmenu/TieredMenuSub.js index fa0e120ab6..e9cf741cf0 100644 --- a/components/lib/tieredmenu/TieredMenuSub.js +++ b/components/lib/tieredmenu/TieredMenuSub.js @@ -1,13 +1,21 @@ import * as React from 'react'; import { useEventListener, useMountEffect, useResizeListener, useUpdateEffect } from '../hooks/Hooks'; import { Ripple } from '../ripple/Ripple'; -import { classNames, DomHandler, IconUtils, ObjectUtils } from '../utils/Utils'; +import { classNames, DomHandler, IconUtils, ObjectUtils, mergeProps } from '../utils/Utils'; import { AngleRightIcon } from '../icons/angleright'; export const TieredMenuSub = React.memo((props) => { const [activeItemState, setActiveItemState] = React.useState(null); const elementRef = React.useRef(null); + const getPTOptions = (item, key) => { + return props.ptm(key, { + context: { + active: activeItemState === item + } + }); + }; + const [bindDocumentClickListener] = useEventListener({ type: 'click', listener: (event) => { @@ -180,7 +188,16 @@ export const TieredMenuSub = React.memo((props) => { const createSeparator = (index) => { const key = 'separator_' + index; - return
  • ; + const separatorProps = mergeProps( + { + key, + className: 'p-menu-separator', + role: 'separator' + }, + props.ptm('separator') + ); + + return
  • ; }; const createSubmenu = (item) => { @@ -196,6 +213,7 @@ export const TieredMenuSub = React.memo((props) => { isMobileMode={props.isMobileMode} onItemToggle={props.onItemToggle} submenuIcon={props.submenuIcon} + ptm={props.ptm} /> ); } @@ -214,13 +232,45 @@ export const TieredMenuSub = React.memo((props) => { const className = classNames('p-menuitem', { 'p-menuitem-active': active }, _className); const linkClassName = classNames('p-menuitem-link', { 'p-disabled': disabled }); const iconClassName = classNames('p-menuitem-icon', _icon); - const icon = IconUtils.getJSXIcon(_icon, { className: 'p-menuitem-icon' }, { props: props.menuProps }); - const label = _label && {_label}; + const iconProps = mergeProps( + { + className: iconClassName + }, + getPTOptions(item, 'icon') + ); + const icon = IconUtils.getJSXIcon(_icon, { ...iconProps }, { props: props.menuProps }); + const labelProps = mergeProps( + { + className: 'p-menuitem-text' + }, + getPTOptions(item, 'label') + ); + const label = _label && {_label}; const submenuIconClassName = 'p-submenu-icon'; - const submenuIcon = item.items && IconUtils.getJSXIcon(props.submenuIcon || , { className: submenuIconClassName }, { props: props.menuProps }); + const submenuIconProps = mergeProps( + { + className: submenuIconClassName + }, + getPTOptions(item, 'submenuIcon') + ); + const submenuIcon = item.items && IconUtils.getJSXIcon(props.submenuIcon || , { ...submenuIconProps }, { props: props.menuProps }); const submenu = createSubmenu(item); + const actionProps = mergeProps( + { + href: url || '#', + className: linkClassName, + target: target, + role: 'menuitem', + 'aria-haspopup': items != null, + onClick: (event) => onItemClick(event, item), + onKeyDown: (event) => onItemKeyDown(event, item), + 'aria-disabled': disabled + }, + getPTOptions(item, 'action') + ); + let content = ( - onItemClick(event, item)} onKeyDown={(event) => onItemKeyDown(event, item)} aria-disabled={disabled}> + {icon} {label} {submenuIcon} @@ -245,8 +295,20 @@ export const TieredMenuSub = React.memo((props) => { content = ObjectUtils.getJSXElement(template, item, defaultContentOptions); } + const menuitemProps = mergeProps( + { + key, + id: item.id, + className, + style: style, + onMouseEnter: (event) => onItemMouseEnter(event, item), + role: 'none' + }, + getPTOptions(item, 'menuitem') + ); + return ( -
  • onItemMouseEnter(event, item)} role="none"> +
  • {content} {submenu}
  • @@ -265,12 +327,17 @@ export const TieredMenuSub = React.memo((props) => { 'p-submenu-list': !props.root }); const submenu = createMenu(); - - return ( -
      - {submenu} -
    + const menuProps = mergeProps( + { + ref: elementRef, + className, + role: props.root ? 'menubar' : 'menu', + 'aria-orientation': 'horizontal' + }, + props.ptm('menu') ); + + return
      {submenu}
    ; }); TieredMenuSub.displayName = 'TieredMenuSub'; diff --git a/components/lib/tieredmenu/tieredmenu.d.ts b/components/lib/tieredmenu/tieredmenu.d.ts index b3cb0ab294..fea3674a5c 100644 --- a/components/lib/tieredmenu/tieredmenu.d.ts +++ b/components/lib/tieredmenu/tieredmenu.d.ts @@ -10,13 +10,89 @@ import * as React from 'react'; import { CSSTransitionProps } from '../csstransition'; import { MenuItem } from '../menuitem'; -import { IconType } from '../utils/utils'; +import { IconType, PassThroughType } from '../utils/utils'; + +export declare type TieredMenuPassThroughType = PassThroughType; + +/** + * Custom passthrough(pt) option method. + */ +export interface TieredMenuPassThroughMethodOptions { + props: TieredMenuProps; + state: TieredMenuState; + context: TieredMenuContext; +} + +/** + * Custom passthrough(pt) options. + * @see {@link TieredMenuProps.pt} + */ +export interface TieredMenuPassThroughOptions { + /** + * Uses to pass attributes to the root's DOM element. + */ + root?: TieredMenuPassThroughType>; + /** + * Uses to pass attributes to the list's DOM element. + */ + menu?: TieredMenuPassThroughType>; + /** + * Uses to pass attributes to the list item's DOM element. + */ + menuitem?: TieredMenuPassThroughType>; + /** + * Uses to pass attributes to the action's DOM element. + */ + action?: TieredMenuPassThroughType>; + /** + * Uses to pass attributes to the icon's DOM element. + */ + icon?: TieredMenuPassThroughType | React.HTMLAttributes>; + /** + * Uses to pass attributes to the label's DOM element. + */ + label?: TieredMenuPassThroughType>; + /** + * Uses to pass attributes to the submenu icon's DOM element. + */ + submenuIcon?: TieredMenuPassThroughType | React.HTMLAttributes>; + /** + * Uses to pass attributes to the separator's DOM element. + */ + separator?: TieredMenuPassThroughType>; +} + +/** + * Defines current inline state in TieredMenu component. + */ +export interface TieredMenuState { + /** + * Current attributeSelector visible state as a string. + */ + attributeSelector: string; + /** + * Current visible state as a boolean. + * @defaultValue true + */ + visible: boolean; +} + +/** + * Defines current options in TieredMenu component. + */ +export interface TieredMenuContext { + /** + * Current active state of menuitem as a boolean. + * @defaultValue false + */ + active: boolean; +} /** * Defines valid properties in TieredMenu component. In addition to these, all properties of HTMLDivElement can be used in this component. * @group Properties */ -export interface TieredMenuProps extends Omit, HTMLDivElement>, 'ref'> { +export interface TieredMenuProps extends Omit, HTMLDivElement>, 'ref' | 'pt'> { /** * An array of menuitems. */ @@ -74,6 +150,11 @@ export interface TieredMenuProps extends Omit { component: AccessibilityDoc } ]; + const ptDocs = [ + { + id: 'pt.wireframe', + label: 'Wireframe', + component: Wireframe + }, + { + id: 'pt.tieredmenu.options', + label: 'TieredMenu PT Options', + component: DocApiTable + }, + { + id: 'pt.demo', + label: 'Example', + component: PTDoc + } + ]; - return ; + return ; }; export default TieredMenuDemo;