diff --git a/components/doc/common/apidoc/index.json b/components/doc/common/apidoc/index.json index a3173b0789..e626765add 100644 --- a/components/doc/common/apidoc/index.json +++ b/components/doc/common/apidoc/index.json @@ -22952,6 +22952,14 @@ "type": "ReactNode", "default": "", "description": "Used to get the child elements of the component." + }, + { + "name": "pt", + "optional": true, + "readonly": false, + "type": "MenuPassThroughOptions", + "default": "", + "description": "Uses to pass attributes to DOM elements inside the component." } ] }, @@ -22987,6 +22995,115 @@ ] } } + }, + "interfaces": { + "description": "Defines the custom interfaces used by the module.", + "values": { + "MenuPassThroughMethodOptions": { + "description": "Custom passthrough(pt) option method.", + "relatedProp": "", + "props": [ + { + "name": "props", + "optional": false, + "readonly": false, + "type": "MenuProps" + }, + { + "name": "state", + "optional": false, + "readonly": false, + "type": "MenuState" + } + ], + "callbacks": [] + }, + "MenuPassThroughOptions": { + "description": "Custom passthrough(pt) options.", + "relatedProp": "pt", + "props": [ + { + "name": "root", + "optional": true, + "readonly": false, + "type": "MenuPassThroughType>", + "description": "Uses to pass attributes to the root's DOM element." + }, + { + "name": "menu", + "optional": true, + "readonly": false, + "type": "MenuPassThroughType>", + "description": "Uses to pass attributes to the list's DOM element." + }, + { + "name": "submenuHeader", + "optional": true, + "readonly": false, + "type": "MenuPassThroughType>", + "description": "Uses to pass attributes to the submenu header's DOM element." + }, + { + "name": "menuitem", + "optional": true, + "readonly": false, + "type": "MenuPassThroughType>", + "description": "Uses to pass attributes to the list item's DOM element." + }, + { + "name": "action", + "optional": true, + "readonly": false, + "type": "MenuPassThroughType>", + "description": "Uses to pass attributes to the action's DOM element." + }, + { + "name": "icon", + "optional": true, + "readonly": false, + "type": "MenuPassThroughType | SVGProps>", + "description": "Uses to pass attributes to the icon's DOM element." + }, + { + "name": "label", + "optional": true, + "readonly": false, + "type": "MenuPassThroughType>", + "description": "Uses to pass attributes to the label's DOM element." + }, + { + "name": "separator", + "optional": true, + "readonly": false, + "type": "MenuPassThroughType>", + "description": "Uses to pass attributes to the separator's DOM element." + } + ], + "callbacks": [] + }, + "MenuState": { + "description": "Defines current inline state in Menu component.", + "relatedProp": "", + "props": [ + { + "name": "visible", + "optional": false, + "readonly": false, + "type": "boolean", + "description": "Current visible state as a boolean." + } + ], + "callbacks": [] + } + } + }, + "types": { + "description": "Defines the custom types used by the module.", + "values": { + "MenuPassThroughType": { + "values": "PassThroughType" + } + } } }, "menubar": { diff --git a/components/doc/menu/pt/ptdoc.js b/components/doc/menu/pt/ptdoc.js new file mode 100644 index 0000000000..a8c9271f9b --- /dev/null +++ b/components/doc/menu/pt/ptdoc.js @@ -0,0 +1,195 @@ +import { useRef } from 'react'; +import { useRouter } from 'next/router'; +import { Menu } from '../../../lib/menu/Menu'; +import { DocSectionCode } from '../../common/docsectioncode'; +import { DocSectionText } from '../../common/docsectiontext'; +import { Toast } from '../../../lib/toast/Toast'; + +export function PTDoc(props) { + const toast = useRef(null); + const router = useRouter(); + const items = [ + { + label: 'Options', + items: [ + { + label: 'Update', + icon: 'pi pi-refresh', + command: () => { + toast.current.show({ severity: 'success', summary: 'Updated', detail: 'Data Updated', life: 3000 }); + } + }, + { + label: 'Delete', + icon: 'pi pi-times', + command: () => { + toast.current.show({ severity: 'warn', summary: 'Delete', detail: 'Data Deleted', life: 3000 }); + } + } + ] + }, + { + label: 'Navigate', + items: [ + { + label: 'React Website', + icon: 'pi pi-external-link', + url: 'https://reactjs.org/' + }, + { + label: 'Router', + icon: 'pi pi-upload', + command: () => { + router.push('/fileupload'); + } + } + ] + } + ]; + + const code = { + basic: ` + +`, + javascript: ` +import React, { useRef } from 'react'; +import { useRouter } from 'next/router'; +import { Menu } from 'primereact/menu'; +import { Toast } from 'primereact/toast'; + +export default function PTDemo() { + const items = [ + { + label: 'Options', + items: [ + { + label: 'Update', + icon: 'pi pi-refresh', + command: () => { + toast.current.show({ severity: 'success', summary: 'Updated', detail: 'Data Updated', life: 3000 }); + } + }, + { + label: 'Delete', + icon: 'pi pi-times', + command: () => { + toast.current.show({ severity: 'warn', summary: 'Delete', detail: 'Data Deleted', life: 3000 }); + } + } + ] + }, + { + label: 'Navigate', + items: [ + { + label: 'React Website', + icon: 'pi pi-external-link', + url: 'https://reactjs.org/' + }, + { + label: 'Router', + icon: 'pi pi-upload', + command: () => { + router.push('/fileupload'); + } + } + ] + } + ]; + + return ( +
+ + +
+ ) +} + `, + typescript: ` +import React, { useRef } from 'react'; +import { useRouter } from 'next/router'; +import { Menu } from 'primereact/menu'; +import { MenuItem } from 'primereact/menuitem'; +import { Toast } from 'primereact/toast'; + +export default function PTDemo() { + const items: MenuItem[] = [ + { + label: 'Options', + items: [ + { + label: 'Update', + icon: 'pi pi-refresh', + command: () => { + toast.current.show({ severity: 'success', summary: 'Updated', detail: 'Data Updated', life: 3000 }); + } + }, + { + label: 'Delete', + icon: 'pi pi-times', + command: () => { + toast.current.show({ severity: 'warn', summary: 'Delete', detail: 'Data Deleted', life: 3000 }); + } + } + ] + }, + { + label: 'Navigate', + items: [ + { + label: 'React Website', + icon: 'pi pi-external-link', + url: 'https://reactjs.org/' + }, + { + label: 'Router', + icon: 'pi pi-upload', + command: () => { + router.push('/fileupload'); + } + } + ] + } + ]; + + return ( +
+ + +
+ ) +} + ` + }; + + return ( + <> + +
+ + +
+ + + ); +} diff --git a/components/doc/menu/pt/wireframe.js b/components/doc/menu/pt/wireframe.js new file mode 100644 index 0000000000..54aafd2e3e --- /dev/null +++ b/components/doc/menu/pt/wireframe.js @@ -0,0 +1,15 @@ + +import React from 'react'; +import { DocSectionText } from '../../common/docsectiontext'; + +export const Wireframe = (props) => { + + return ( + <> + +
+ menu +
+ + ); +}; diff --git a/components/lib/menu/Menu.js b/components/lib/menu/Menu.js index 3d0a9973d2..d52a935df6 100644 --- a/components/lib/menu/Menu.js +++ b/components/lib/menu/Menu.js @@ -4,14 +4,19 @@ import { CSSTransition } from '../csstransition/CSSTransition'; import { useOverlayListener, useUnmountEffect } from '../hooks/Hooks'; import { OverlayService } from '../overlayservice/OverlayService'; import { Portal } from '../portal/Portal'; -import { DomHandler, IconUtils, ObjectUtils, ZIndexUtils, classNames } from '../utils/Utils'; +import { classNames, DomHandler, IconUtils, mergeProps, ObjectUtils, ZIndexUtils } from '../utils/Utils'; import { MenuBase } from './MenuBase'; export const Menu = React.memo( React.forwardRef((inProps, ref) => { const props = MenuBase.getProps(inProps); - const [visibleState, setVisibleState] = React.useState(!props.popup); + const { ptm } = MenuBase.setMetaData({ + props, + state: { + visible: visibleState + } + }); const menuRef = React.useRef(null); const targetRef = React.useRef(null); @@ -152,10 +157,18 @@ export const Menu = React.memo( submenu.className ); const items = submenu.items.map(createMenuItem); + const submenuHeaderProps = mergeProps( + { + className, + style: submenu.style, + role: "presentation" + }, + ptm('submenuHeader') + ); return ( -
  • +
  • {submenu.label}
  • {items} @@ -165,8 +178,16 @@ export const Menu = React.memo( const createSeparator = (index) => { const key = 'separator_' + index; + const separatorProps = mergeProps( + { + key, + className: "p-menu-separator", + role: "separator" + }, + ptm('separator') + ) - return
  • ; + return
  • ; }; const createMenuItem = (item, index) => { @@ -177,21 +198,38 @@ export const Menu = React.memo( const className = classNames('p-menuitem', item.className); const linkClassName = classNames('p-menuitem-link', { 'p-disabled': item.disabled }); const iconClassName = classNames('p-menuitem-icon', item.icon); - const icon = IconUtils.getJSXIcon(item.icon, { className: 'p-menuitem-icon' }, { props }); - const label = item.label && {item.label}; + const iconProps = mergeProps( + { + className: 'p-menuitem-icon' + }, + ptm('icon') + ); + const icon = IconUtils.getJSXIcon(item.icon, { ...iconProps }, { props }); + const labelProps = mergeProps( + { + className: "p-menuitem-text" + }, + ptm('label') + ); + const label = item.label && {item.label}; const tabIndex = item.disabled ? null : 0; const key = item.label + '_' + index; + const actionProps = mergeProps( + { + href: item.url || '#', + className: linkClassName, + role: "menuitem", + target: item.target, + onClick: (event) => onItemClick(event, item), + onKeyDown: (event) => onItemKeyDown(event, item), + tabIndex: tabIndex, + 'aria-disabled': item.disabled, + }, + ptm('action') + ); + let content = ( - onItemClick(event, item)} - onKeyDown={(event) => onItemKeyDown(event, item)} - tabIndex={tabIndex} - aria-disabled={item.disabled} - > + {icon} {label} @@ -212,8 +250,18 @@ export const Menu = React.memo( content = ObjectUtils.getJSXElement(item.template, item, defaultContentOptions); } + const menuitemProps = mergeProps( + { + key, + className, + style: item.style, + role: "none" + }, + ptm('menuitem') + ); + return ( -
  • +
  • {content}
  • ); @@ -229,7 +277,6 @@ export const Menu = React.memo( const createElement = () => { if (props.model) { - const otherProps = MenuBase.getOtherProps(props); const className = classNames( 'p-menu p-component', { @@ -240,6 +287,25 @@ export const Menu = React.memo( props.className ); const menuitems = createMenu(); + const rootProps = mergeProps( + { + ref: menuRef, + id: props.id, + className, + style: props.style, + onClick: (e) => onPanelClick(e) + }, + MenuBase.getOtherProps(props), + ptm('root') + ); + + const menuProps = mergeProps( + { + className: "p-menu-list p-reset", + role: "menu" + }, + ptm('menu') + ); return ( -
    -
      +
      +
        {menuitems}
      diff --git a/components/lib/menu/MenuBase.js b/components/lib/menu/MenuBase.js index d606afbf71..e5feafcf44 100644 --- a/components/lib/menu/MenuBase.js +++ b/components/lib/menu/MenuBase.js @@ -1,6 +1,6 @@ -import { ObjectUtils } from '../utils/Utils'; +import { ComponentBase } from '../componentbase/ComponentBase'; -export const MenuBase = { +export const MenuBase = ComponentBase.extend({ defaultProps: { __TYPE: 'Menu', id: null, @@ -16,7 +16,5 @@ export const MenuBase = { onShow: null, onHide: null, children: undefined - }, - getProps: (props) => ObjectUtils.getMergedProps(props, MenuBase.defaultProps), - getOtherProps: (props) => ObjectUtils.getDiffProps(props, MenuBase.defaultProps) -}; + } +}); diff --git a/components/lib/menu/menu.d.ts b/components/lib/menu/menu.d.ts index 0aa1b211fd..ffc57c1e2b 100644 --- a/components/lib/menu/menu.d.ts +++ b/components/lib/menu/menu.d.ts @@ -10,6 +10,67 @@ import * as React from 'react'; import { CSSTransitionProps } from '../csstransition'; import { MenuItem } from '../menuitem'; +import { PassThroughType } from '../utils/utils'; + +export declare type MenuPassThroughType = PassThroughType; + +/** + * Custom passthrough(pt) option method. + */ +export interface MenuPassThroughMethodOptions { + props: MenuProps; + state: MenuState; +} + +/** + * Custom passthrough(pt) options. + * @see {@link MenuProps.pt} + */ +export interface MenuPassThroughOptions { + /** + * Uses to pass attributes to the root's DOM element. + */ + root?: MenuPassThroughType>; + /** + * Uses to pass attributes to the list's DOM element. + */ + menu?: MenuPassThroughType>; + /** + * Uses to pass attributes to the submenu header's DOM element. + */ + submenuHeader?: MenuPassThroughType>; + /** + * Uses to pass attributes to the list item's DOM element. + */ + menuitem?: MenuPassThroughType>; + /** + * Uses to pass attributes to the action's DOM element. + */ + action?: MenuPassThroughType>; + /** + * Uses to pass attributes to the icon's DOM element. + */ + icon?: MenuPassThroughType | React.HTMLAttributes>; + /** + * Uses to pass attributes to the label's DOM element. + */ + label?: MenuPassThroughType>; + /** + * Uses to pass attributes to the separator's DOM element. + */ + separator?: MenuPassThroughType>; +} + +/** + * Defines current inline state in Menu component. + */ +export interface MenuState { + /** + * Current visible state as a boolean. + * @defaultValue true + */ + visible: boolean; +} /** * Defines valid properties in Menu component. In addition to these, all properties of HTMLDivElement can be used in this component. @@ -65,6 +126,11 @@ export interface MenuProps extends Omit { component: AccessibilityDoc } ]; +const ptDocs = [ + { + id: 'pt.wireframe', + label: 'Wireframe', + component: Wireframe + }, + { + id: 'pt.menu.options', + label: 'Menu PT Options', + component: DocApiTable + }, + { + id: 'pt.demo', + label: 'Example', + component: PTDoc + } +]; - return ; + + return ; }; export default MenuDemo;