From 2b16b45ede43141b187bad676b4ed5de78800de4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ula=C5=9F=20Turan?= Date: Thu, 18 May 2023 00:35:16 +0300 Subject: [PATCH] Refactor #4391 - For Menubar --- components/doc/common/apidoc/index.json | 164 +++++++++ components/doc/menubar/pt/ptdoc.js | 454 ++++++++++++++++++++++++ components/doc/menubar/pt/wireframe.js | 13 + components/lib/menubar/Menubar.js | 64 +++- components/lib/menubar/MenubarBase.js | 10 +- components/lib/menubar/MenubarSub.js | 102 +++++- components/lib/menubar/menubar.d.ts | 95 ++++- pages/menubar/index.js | 22 +- 8 files changed, 887 insertions(+), 37 deletions(-) create mode 100644 components/doc/menubar/pt/ptdoc.js create mode 100644 components/doc/menubar/pt/wireframe.js diff --git a/components/doc/common/apidoc/index.json b/components/doc/common/apidoc/index.json index e626765add..40fd3fbcd3 100644 --- a/components/doc/common/apidoc/index.json +++ b/components/doc/common/apidoc/index.json @@ -23192,6 +23192,170 @@ "values": [] } } + }, + "interfaces": { + "description": "Defines the custom interfaces used by the module.", + "values": { + "MenubarPassThroughMethodOptions": { + "description": "Custom passthrough(pt) option method.", + "relatedProp": "", + "props": [ + { + "name": "props", + "optional": false, + "readonly": false, + "type": "MenubarProps" + }, + { + "name": "state", + "optional": false, + "readonly": false, + "type": "MenubarState" + }, + { + "name": "context", + "optional": false, + "readonly": false, + "type": "MenubarContext" + } + ], + "callbacks": [] + }, + "MenubarPassThroughOptions": { + "description": "Custom passthrough(pt) options.", + "relatedProp": "MenubarProps.pt", + "props": [ + { + "name": "root", + "optional": true, + "readonly": false, + "type": "MenubarPassThroughType>", + "description": "Uses to pass attributes to the root's DOM element." + }, + { + "name": "menu", + "optional": true, + "readonly": false, + "type": "MenubarPassThroughType>", + "description": "Uses to pass attributes to the list's DOM element." + }, + { + "name": "menuitem", + "optional": true, + "readonly": false, + "type": "MenubarPassThroughType>", + "description": "Uses to pass attributes to the list item's DOM element." + }, + { + "name": "content", + "optional": true, + "readonly": false, + "type": "MenubarPassThroughType>", + "description": "Uses to pass attributes to the content's DOM element." + }, + { + "name": "action", + "optional": true, + "readonly": false, + "type": "MenubarPassThroughType>", + "description": "Uses to pass attributes to the action's DOM element." + }, + { + "name": "icon", + "optional": true, + "readonly": false, + "type": "MenubarPassThroughType>", + "description": "Uses to pass attributes to the icon's DOM element." + }, + { + "name": "label", + "optional": true, + "readonly": false, + "type": "MenubarPassThroughType>", + "description": "Uses to pass attributes to the label's DOM element." + }, + { + "name": "submenuIcon", + "optional": true, + "readonly": false, + "type": "MenubarPassThroughType>", + "description": "Uses to pass attributes to the submenu icon's DOM element." + }, + { + "name": "separator", + "optional": true, + "readonly": false, + "type": "MenubarPassThroughType>", + "description": "Uses to pass attributes to the separator's DOM element." + }, + { + "name": "button", + "optional": true, + "readonly": false, + "type": "MenubarPassThroughType>", + "description": "Uses to pass attributes to the mobile popup menu button's DOM element." + }, + { + "name": "popupIcon", + "optional": true, + "readonly": false, + "type": "MenubarPassThroughType>", + "description": "Uses to pass attributes to the mobile popup menu button icon's DOM element." + }, + { + "name": "start", + "optional": true, + "readonly": false, + "type": "MenubarPassThroughType>", + "description": "Uses to pass attributes to the start of the component." + }, + { + "name": "end", + "optional": true, + "readonly": false, + "type": "MenubarPassThroughType>", + "description": "Uses to pass attributes to the end of the component." + } + ], + "callbacks": [] + }, + "MenubarState": { + "description": "Defines current inline state in Menubar component.", + "relatedProp": "", + "props": [ + { + "name": "mobileActive", + "optional": false, + "readonly": false, + "type": "boolean", + "description": "Current mobile menu active state as a boolean." + } + ], + "callbacks": [] + }, + "MenubarContext": { + "description": "Defines current options in Menubar 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": { + "MenubarPassThroughType": { + "values": "PassThroughType" + } + } } }, "menuitem": { diff --git a/components/doc/menubar/pt/ptdoc.js b/components/doc/menubar/pt/ptdoc.js new file mode 100644 index 0000000000..8a9e623d57 --- /dev/null +++ b/components/doc/menubar/pt/ptdoc.js @@ -0,0 +1,454 @@ +import { Menubar } from '../../../lib/menubar/Menubar'; +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' + } + ] + } + ] + }, + { + label: 'Quit', + icon: 'pi pi-fw pi-power-off' + } + ]; + + const code = { + basic: ` + ({ + className: context.active ? 'bg-primary-200 border-round-sm' : undefined + }) + }} +/> +`, + javascript: ` +import React from 'react'; +import { Menubar } from 'primereact/menubar'; + +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' + } + ] + } + ] + }, + { + label: 'Quit', + icon: 'pi pi-fw pi-power-off' + } + ]; + + return ( +
+ ({ + className: context.active ? 'bg-primary-200 border-round-sm' : undefined + }) + }} + /> +
+ ) +} + `, + typescript: ` +import React from 'react'; +import { Menubar } from 'primereact/menubar'; +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' + } + ] + } + ] + }, + { + label: 'Quit', + icon: 'pi pi-fw pi-power-off' + } + ]; + + return ( +
+ ({ + className: context.active ? 'bg-primary-200 border-round-sm' : undefined + }) + }} + /> +
+ ) +} + ` + }; + + return ( + <> + +
+ ({ + className: context.active ? 'bg-primary-200 border-round-sm' : undefined + }) + }} + /> +
+ + + ); +} diff --git a/components/doc/menubar/pt/wireframe.js b/components/doc/menubar/pt/wireframe.js new file mode 100644 index 0000000000..c5195ba128 --- /dev/null +++ b/components/doc/menubar/pt/wireframe.js @@ -0,0 +1,13 @@ +import React from 'react'; +import { DocSectionText } from '../../common/docsectiontext'; + +export const Wireframe = (props) => { + return ( + <> + +
+ menubar +
+ + ); +}; diff --git a/components/lib/menubar/Menubar.js b/components/lib/menubar/Menubar.js index 0aada8302c..64ae2de93e 100644 --- a/components/lib/menubar/Menubar.js +++ b/components/lib/menubar/Menubar.js @@ -1,7 +1,7 @@ import * as React from 'react'; import PrimeReact from '../api/Api'; import { useEventListener, useUnmountEffect, useUpdateEffect } from '../hooks/Hooks'; -import { classNames, IconUtils, ObjectUtils, ZIndexUtils } from '../utils/Utils'; +import { classNames, IconUtils, mergeProps, ObjectUtils, ZIndexUtils } from '../utils/Utils'; import { MenubarBase } from './MenubarBase'; import { MenubarSub } from './MenubarSub'; import { BarsIcon } from '../icons/bars'; @@ -14,6 +14,12 @@ export const Menubar = React.memo( const elementRef = React.useRef(null); const rootMenuRef = React.useRef(null); const menuButtonRef = React.useRef(null); + const { ptm } = MenubarBase.setMetaData({ + props, + state: { + mobileActive: mobileActiveState + } + }); const [bindDocumentClickListener, unbindDocumentClickListener] = useEventListener({ type: 'click', @@ -64,8 +70,14 @@ export const Menubar = React.memo( const createStartContent = () => { if (props.start) { const start = ObjectUtils.getJSXElement(props.start, props); - - return
{start}
; + const startProps = mergeProps( + { + className: 'p-menubar-start' + }, + ptm('start') + ); + + return
{start}
; } return null; @@ -74,8 +86,14 @@ export const Menubar = React.memo( const createEndContent = () => { if (props.end) { const end = ObjectUtils.getJSXElement(props.end, props); - - return
{end}
; + const endProps = mergeProps( + { + className: 'p-menubar-end' + }, + ptm('end') + ); + + return
{end}
; } return null; @@ -86,20 +104,27 @@ export const Menubar = React.memo( return null; } - const icon = props.menuIcon || ; - const menuIcon = IconUtils.getJSXIcon(icon, undefined, { props }); - /* eslint-disable */ - const button = ( - - {menuIcon} - + const buttonProps = mergeProps( + { + ref: menuButtonRef, + href: '#', + role: 'button', + tabIndex: 0, + className: 'p-menubar-button', + onClick: (e) => toggle(e) + }, + ptm('button') ); + const icon = props.menuIcon || ; + const menuIcon = IconUtils.getJSXIcon(icon, { ...buttonProps }, { props }); + + /* eslint-disable */ + const button = {menuIcon}; /* eslint-enable */ return button; }; - const otherProps = MenubarBase.getOtherProps(props); const className = classNames( 'p-menubar p-component', { @@ -110,10 +135,19 @@ export const Menubar = React.memo( const start = createStartContent(); const end = createEndContent(); const menuButton = createMenuButton(); - const submenu = ; + const submenu = ; + const rootProps = mergeProps( + { + id: props.id, + className, + style: props.style + }, + MenubarBase.getOtherProps(props), + ptm('root') + ); return ( -
+
{start} {menuButton} {submenu} diff --git a/components/lib/menubar/MenubarBase.js b/components/lib/menubar/MenubarBase.js index 2bc4427644..1237c6414b 100644 --- a/components/lib/menubar/MenubarBase.js +++ b/components/lib/menubar/MenubarBase.js @@ -1,6 +1,6 @@ -import { ObjectUtils } from '../utils/Utils'; +import { ComponentBase } from '../componentbase/ComponentBase'; -export const MenubarBase = { +export const MenubarBase = ComponentBase.extend({ defaultProps: { __TYPE: 'Menubar', id: null, @@ -12,7 +12,5 @@ export const MenubarBase = { menuIcon: null, end: null, children: undefined - }, - getProps: (props) => ObjectUtils.getMergedProps(props, MenubarBase.defaultProps), - getOtherProps: (props) => ObjectUtils.getDiffProps(props, MenubarBase.defaultProps) -}; + } +}); diff --git a/components/lib/menubar/MenubarSub.js b/components/lib/menubar/MenubarSub.js index 7109af9027..1d5a99ce00 100644 --- a/components/lib/menubar/MenubarSub.js +++ b/components/lib/menubar/MenubarSub.js @@ -1,7 +1,7 @@ import * as React from 'react'; import { useEventListener, useMountEffect, useUpdateEffect } from '../hooks/Hooks'; import { Ripple } from '../ripple/Ripple'; -import { classNames, DomHandler, IconUtils, ObjectUtils } from '../utils/Utils'; +import { classNames, DomHandler, IconUtils, mergeProps, ObjectUtils } from '../utils/Utils'; import { AngleRightIcon } from '../icons/angleright'; import { AngleDownIcon } from '../icons/angledown'; @@ -9,6 +9,14 @@ export const MenubarSub = React.memo( React.forwardRef((props, ref) => { const [activeItemState, setActiveItemState] = React.useState(null); + const getPTOptions = (item, key) => { + return props.ptm(key, { + context: { + active: activeItemState === item + } + }); + }; + const [bindDocumentClickListener] = useEventListener({ type: 'click', listener: (event) => { @@ -166,13 +174,32 @@ export const MenubarSub = React.memo( const createSeparator = (index) => { const key = 'separator_' + index; + const separatorProps = mergeProps( + { + key, + className: 'p-menu-separator', + role: 'separator' + }, + props.ptm('separator') + ); - return
  • ; + return
  • ; }; const createSubmenu = (item) => { if (item.items) { - return ; + return ( + + ); } return null; @@ -187,19 +214,50 @@ export const MenubarSub = React.memo( const className = classNames('p-menuitem', { 'p-menuitem-active': activeItemState === item }, 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: props.menuProps }); - const label = item.label && {item.label}; + const iconProps = mergeProps( + { + className: 'p-menuitem-icon' + }, + getPTOptions(item, 'icon') + ); + const icon = IconUtils.getJSXIcon(item.icon, { ...iconProps }, { props: props.menuProps }); + const labelProps = mergeProps( + { + className: 'p-menuitem-text' + }, + getPTOptions(item, 'label') + ); + const label = item.label && {item.label}; const submenuIconClassName = 'p-submenu-icon'; + const submenuIconProps = mergeProps( + { + className: submenuIconClassName + }, + getPTOptions(item, 'submenuIcon') + ); const submenuIcon = item.items && IconUtils.getJSXIcon( - !props.root ? props.submenuIcon || : props.submenuIcon || , - { className: submenuIconClassName }, + !props.root ? props.submenuIcon || : props.submenuIcon || , + { ...submenuIconProps }, { props: { menuProps: props.menuProps, ...props } } ); const submenu = createSubmenu(item); + const actionProps = mergeProps( + { + href: item.url || '#', + role: 'menuitem', + className: linkClassName, + target: item.target, + 'aria-haspopup': item.items != null, + onClick: (event) => onItemClick(event, item), + onKeyDown: (event) => onItemKeyDown(event, item) + }, + getPTOptions(item, 'action') + ); + let content = ( - onItemClick(event, item)} onKeyDown={(event) => onItemKeyDown(event, item)}> + {icon} {label} {submenuIcon} @@ -222,8 +280,20 @@ export const MenubarSub = React.memo( content = ObjectUtils.getJSXElement(item.template, item, defaultContentOptions); } + const menuitemProps = mergeProps( + { + key, + role: 'none', + id: item.id, + className, + style: item.style, + onMouseEnter: (event) => onItemMouseEnter(event, item) + }, + getPTOptions(item, 'menuitem') + ); + return ( -
  • onItemMouseEnter(event, item)}> +
  • {content} {submenu}
  • @@ -244,12 +314,16 @@ export const MenubarSub = React.memo( 'p-menubar-root-list': props.root }); const submenu = createMenu(); - - return ( -
      - {submenu} -
    + const menuProps = mergeProps( + { + ref, + className, + role + }, + props.ptm('menu') ); + + return
      {submenu}
    ; }) ); diff --git a/components/lib/menubar/menubar.d.ts b/components/lib/menubar/menubar.d.ts index c71b5b705b..af4b3b69f0 100644 --- a/components/lib/menubar/menubar.d.ts +++ b/components/lib/menubar/menubar.d.ts @@ -9,7 +9,95 @@ */ import * as React from 'react'; import { MenuItem } from '../menuitem'; -import { IconType } from '../utils/utils'; +import { IconType, PassThroughType } from '../utils/utils'; + +export declare type MenubarPassThroughType = PassThroughType; + +/** + * Custom passthrough(pt) option method. + */ +export interface MenubarPassThroughMethodOptions { + props: MenubarProps; + state: MenubarState; + context: MenubarContext; +} + +/** + * Custom passthrough(pt) options. + * @see {@link MenubarProps.pt} + */ +export interface MenubarPassThroughOptions { + /** + * Uses to pass attributes to the root's DOM element. + */ + root?: MenubarPassThroughType>; + /** + * Uses to pass attributes to the list's DOM element. + */ + menu?: MenubarPassThroughType>; + /** + * Uses to pass attributes to the list item's DOM element. + */ + menuitem?: MenubarPassThroughType>; + /** + * Uses to pass attributes to the action's DOM element. + */ + action?: MenubarPassThroughType>; + /** + * Uses to pass attributes to the icon's DOM element. + */ + icon?: MenubarPassThroughType | React.HTMLAttributes>; + /** + * Uses to pass attributes to the label's DOM element. + */ + label?: MenubarPassThroughType>; + /** + * Uses to pass attributes to the submenu icon's DOM element. + */ + submenuIcon?: MenubarPassThroughType | React.HTMLAttributes>; + /** + * Uses to pass attributes to the separator's DOM element. + */ + separator?: MenubarPassThroughType>; + /** + * Uses to pass attributes to the mobile popup menu button's DOM element. + */ + button?: MenubarPassThroughType>; + /** + * Uses to pass attributes to the mobile popup menu button icon's DOM element. + */ + popupIcon?: MenubarPassThroughType>; + /** + * Uses to pass attributes to the start of the component. + */ + start?: MenubarPassThroughType>; + /** + * Uses to pass attributes to the end of the component. + */ + end?: MenubarPassThroughType>; +} + +/** + * Defines current inline state in Menubar component. + */ +export interface MenubarState { + /** + * Current mobile menu active state as a boolean. + * @defaultValue false + */ + mobileActive: boolean; +} + +/** + * Defines current options in Menubar component. + */ +export interface MenubarContext { + /** + * Current active state of menuitem as a boolean. + * @defaultValue false + */ + active: boolean; +} /** * Defines valid properties in Menubar component. In addition to these, all properties of HTMLDivElement can be used in this component. @@ -41,6 +129,11 @@ export interface MenubarProps extends Omit { component: AccessibilityDoc } ]; + const ptDocs = [ + { + id: 'pt.wireframe', + label: 'Wireframe', + component: Wireframe + }, + { + id: 'pt.menubar.options', + label: 'Menubar PT Options', + component: DocApiTable + }, + { + id: 'pt.demo', + label: 'Example', + component: PTDoc + } + ]; - return ; + return ; }; export default MenubarDemo;