Icons panelwindow.EasySvgPanel.registerTab

window.EasySvgPanel.registerTab()

Registers your own tab in the Easy SVG Icons panel: an id, a render function that returns a React element, and optional label, area, order and icon.

JavaScript · Easy SVG · Status: Stable

registerTab() adds a screen of your own to the Icons panel. You give it an id and a function that returns what the tab shows, and the panel puts the tab into its left rail, in the area you choose. Register the same id again and your new version replaces the old one.

The panel draws a tab from another add-on only while Easy SVG Pro is active and licensed. On a site with the free plugin alone, registerTab() accepts your tab but the panel does not show it. If your add-on must work without Pro, put your icons into the Icon library with registerCollections() instead, which has no such condition.

Usage

window.EasySvgPanel.registerTab( {
	id: 'myaddon-report', // required
	render: ( ctx ) => element, // required
	label: 'SVG report',
	area: 'inspect',
	order: 30,
	icon: 'audit',
} );

When to use it

  • Your add-on needs a full screen inside the Icons panel: a report, a tool, a settings form of its own.
  • You want that screen next to Easy SVG's own tabs instead of a separate admin page, with the same look and the same menu.
  • Combined with easy_svg_panel_menu_tabs, you want the tab reachable from the Icons menu and, if you like, by users without manage_options.

When not to use it:

  • To show icons from your add-on. The Icon library tab can list them beside the site's own icons through registerCollections().
  • On sites where Easy SVG Pro is not licensed, because the tab will not be drawn there (see the callout above).
  • To decide who may use the tab. The capability is declared in PHP, not here.

Parameters

body
tab.idstring
Required

A unique id for the tab. It becomes the value of ?tab= in the address bar and must match the key you use in easy_svg_panel_menu_tabs, because the panel looks up the tab's capability by this id. Use lowercase letters, digits, _ and -, prefixed with your add-on's slug. The ids library, settings, upsell, sets, audit, configurator and licence are taken.

body
tab.renderfunction
Required

Called with one argument, ctx, and must return something React can render: an element, a string or null. Return an element of your own component, for example ( ctx ) => wp.element.createElement( MyTab, { ctx } ), and keep state, effects and requests inside that component. The ctx object holds:

  • restRoot (string): the REST API root with a trailing slash, for example https://example.com/wp-json/. With plain permalinks it is the ?rest_route=/ form, so append your route to it rather than building the URL yourself.
  • nonce (string): a wp_rest nonce. Send it as the X-WP-Nonce header.
  • proActive (boolean): Easy SVG Pro is installed and active.
  • proLicensed (boolean): Easy SVG Pro reports a valid licence.
  • iconsEnabled (boolean): the icon library feature is switched on.
  • iconBlock (boolean): the core Icon block is available (WordPress 7.1 icon registry).
  • maxMbCeiling: the highest upload cap the site allows, in megabytes. It passes through wp_localize_script(), so it can arrive as a string; wrap it in Number().
  • onActivate (function or undefined): switches to Pro's licence tab when there is one.
body
tab.labelstring

The text in the rail. Falls back to the id. Translate it with wp.i18n.__() and your own text domain. It is separate from the menu label you pass in PHP, so keep both the same.

body
tab.areastring

Which section of the rail the tab goes into. One of icons, design, inspect, security or setup, shown as Icons, Design, Inspect, Security and Setup. A missing or unknown value puts the tab into setup. Contract level 2. The older name group is still read as an alias when it holds one of these values.

body
tab.ordernumber

The position within the area, lower first. Default 50. For orientation: Easy SVG's own tabs use 10 and 20, the Pro upsell tab uses 90. Ties keep registration order.

body
tab.iconstring or React children

The rail icon. Either a keyword of the panel's own set (settings, library, sets, configurator, audit, licence, packs, features, styles, bulk, history, upsell) or your own drawing as children of an svg element with viewBox="0 0 24 24", fill="none", stroke="currentColor" and a stroke width of 1.5. An unknown keyword draws an empty square. Contract level 2.

Return value

returnundefined

Nothing. There is no error and no return value when the tab is rejected; check with window.EasySvgPanel.get( id ) if you need to know.

Default

FieldWhen you leave it out
labelthe tab id
areasetup
order50
iconempty icon box
capabilitymanage_options, unless easy_svg_panel_menu_tabs names another one for this id

Examples

A basic tab

No build step: wp.element is the React that WordPress ships and the panel uses. Enqueue this file through easy_svg_panel_enqueue with the panel handle, wp-element and wp-i18n as dependencies.

( function () {
	const panel = window.EasySvgPanel;
	if ( ! panel ) {
		return;
	}

	const { createElement: el } = window.wp.element;
	const { __ } = window.wp.i18n;

	function HelloTab() {
		return el( 'p', null, __( 'Hello from my add-on.', 'myaddon' ) );
	}

	panel.registerTab( {
		id: 'myaddon-hello',
		label: __( 'Hello', 'myaddon' ),
		area: 'setup',
		order: 60,
		render: () => el( HelloTab ),
	} );
} )();

A tab that loads data from your REST route

ctx carries everything a request needs. The component aborts its request when the user switches tabs, because the panel unmounts tabs that are not open.

( function () {
	const panel = window.EasySvgPanel;
	if ( ! panel ) {
		return;
	}

	const { createElement: el, useEffect, useState } = window.wp.element;
	const { __ } = window.wp.i18n;

	function ReportTab( { ctx } ) {
		const [ rows, setRows ] = useState( null );
		const [ error, setError ] = useState( '' );

		useEffect( () => {
			const controller = new AbortController();

			fetch( ctx.restRoot + 'myaddon/v1/report', {
				credentials: 'same-origin',
				headers: { 'X-WP-Nonce': ctx.nonce },
				signal: controller.signal,
			} )
				.then( ( response ) =>
					response.ok ? response.json() : Promise.reject( new Error( response.statusText ) )
				)
				.then( ( data ) => setRows( Array.isArray( data ) ? data : [] ) )
				.catch( ( e ) => {
					if ( 'AbortError' !== e.name ) {
						setError( e.message );
					}
				} );

			return () => controller.abort();
		}, [ ctx.restRoot, ctx.nonce ] );

		if ( error ) {
			return el( 'p', { role: 'alert' }, error );
		}
		if ( null === rows ) {
			return el( 'p', null, __( 'Loading…', 'myaddon' ) );
		}
		return el(
			'ul',
			null,
			rows.map( ( row ) => el( 'li', { key: row.id }, row.title ) )
		);
	}

	panel.registerTab( {
		id: 'myaddon-report',
		label: __( 'SVG report', 'myaddon' ),
		area: 'inspect',
		order: 30,
		icon: 'audit',
		render: ( ctx ) => el( ReportTab, { ctx } ),
	} );
} )();

Defensive: older panels and your own icon

area and a custom icon need contract level 2. On level 1 the tab still works and lands in Setup.

( function () {
	const panel = window.EasySvgPanel;
	if ( ! panel || 'function' !== typeof panel.registerTab ) {
		return;
	}

	const { createElement: el } = window.wp.element;
	const level = Number( panel.api || 1 );

	const tab = {
		id: 'myaddon-report',
		label: 'SVG report',
		render: () => el( 'p', null, 'Report' ),
	};

	if ( 2 <= level ) {
		tab.area = 'inspect';
		tab.order = 30;
		// A check mark, drawn on the panel's 24 by 24 grid.
		tab.icon = el( 'path', { d: 'M5 12l4 4L19 7' } );
	}

	panel.registerTab( tab );
} )();

How it works

registerTab() itself only stores the object in a map keyed by id and notifies the panel. Everything else happens when the panel renders (src/panel/App.jsx):

  1. Collecting. The panel starts with its own tabs (library, settings), the Pro catalogue (sets, audit, configurator, licence) and the upsell tab. Registered tabs with any other id are added as extra tabs, but only when EasySvgPanelData.proLicensed is true. A registered tab with a catalogue id replaces the locked placeholder of that Pro tab, once the licence is valid (the licence tab at any time, so a key can be entered).
  2. Normalising. Each extra tab gets its area (area, else the legacy group, else setup), its order (Number( order ) when finite, else 50) and its label (label, else the id).
  3. Filtering by capability. A tab stays only when the user holds caps[ tabCaps[ id ] ], falling back to manage_options for ids that have no entry in easy_svg_panel_menu_tabs.
  4. Sorting. By area (icons, design, inspect, security, setup), then by order.
  5. Rendering. The rail draws a button per tab. For the content, the panel calls every tab's render( ctx ) on each render of the panel, but the tabs component mounts only the open tab (lazyMount and unmountOnExit). Switching tabs unmounts the previous one.
  6. Choosing the open tab. The ?tab= value in the address bar wins; an id that is not in the list opens the first tab. Clicking a tab rewrites ?tab= with history.replaceState().

Pitfalls

  • No Pro licence, no tab. See the callout at the top. Only add your menu entry when the tab will be drawn, otherwise the entry opens the first tab.
  • No hooks directly in render. The panel calls render( ctx ) for every tab on every render, as a plain function, so hooks called there would belong to the panel's own component and break as soon as the number of tabs changes. Put useState, useEffect and requests inside a component and return an element of it.
  • Keep render cheap and free of side effects. It runs even for tabs that are closed.
  • State is lost when the user switches tabs. The tab unmounts. Keep unsaved input short, save it, or store it outside the component.
  • Abort your requests in the cleanup. Otherwise a slow answer arrives after the user left and can overwrite newer state.
  • Effects run twice with SCRIPT_DEBUG. The panel renders in React's StrictMode; with the development build of React every effect mounts, unmounts and mounts again.
  • Use WordPress's React. wp.element or window.React. A React copy bundled into your script breaks hooks.
  • render must not return a DOM node. If you want to draw with plain DOM code, return an element with a ref callback and fill the node there.
  • Collisions. Registering settings, library or upsell is ignored. Registering sets, audit, configurator or licence takes over a Pro tab. The last registration of an id wins.
  • ctx values from PHP are not always typed. maxMbCeiling can be a string; the booleans are already converted.

Your tab is admin UI and runs with the rights of whoever opens it. React escapes text you render, but dangerouslySetInnerHTML does not: never put SVG or HTML from a request into it unless it went through a sanitizer such as easy_svg_sanitizer() on the server. Every route your tab calls must check current_user_can() itself; the capability in easy_svg_panel_menu_tabs only hides the tab.