社区新闻

超越方块样式,第三部分:构建自定义设计工具

查看官方原文 ↗ 发布于

我们共同进行的这段旅程即将抵达终点。我很期待你在阅读完本系列最后一篇文章后能创造出什么。

你错过了前两篇帖子吗?没问题。请查看这些链接,完成后再回来阅读这篇文章:

本教程是你前两课所有辛勤工作的结晶。你首先学习了如何在主题中使用 WordPress 脚本包及其原因。然后你为 Separator 区块设计了一些自定义图标样式。在这个过程中,你遇到了 Block Styles API 的一些限制。

现在,让我们更进一步,构建一个自定义编辑器控件,让你的用户可以为 Separator 区块分配图标。

你可以从 Beyond Block Styles GitHub 仓库的 part-3 分支获取本教程的最终代码。我建议你仅将其作为本教程的补充参考。

请记住,你的最终成品将如下所示:

WordPress 文章编辑器,其中高亮显示了一个 Separator 区块。工具栏中有一个弹出模态框,其中包含一个可供选择的 emoji 图标网格。

准备工作

在第二部分结束时,我要求你删除 /resources/js/editor.js 文件中的所有代码,但保留你添加的所有其他代码。如果你当时没有这样做,请现在完成。

首先,在 /resources/js 文件夹下添加两个空的 JavaScript 文件,分别命名为 control-icon.jsutil.js。请记住,将代码分解成更易于处理的块是一种好习惯。

添加上述文件后,你的 /resources 文件夹结构应如下所示:

  • /resources
    • /js
      • const.js
      • control-icons.js
      • editor.js
      • util.js
    • /scss
      • editor.scss
      • screen.scss

现在安装 WordPress 图标库。方便的是,它是一个可以通过单个命令安装的包,就像我们之前安装脚本包一样。为此,打开你喜欢的命令行编辑器,导航到你的主题文件夹。然后输入以下命令:

npm install @wordpress/icons --save-dev

现在你可以在脚本中使用 WordPress 图标库了。稍后添加工具栏图标时会需要它。

是时候进入真正激动人心的部分了:为 Separator 区块构建一个自定义图标选择器。

别忘了启用 watch 模式,以便 webpack 编译你的代码:

npm run start

使用 JavaScript 创建自定义控件

这是本教程系列中最大的部分。

这是你第一次为 WordPress 编辑器编写 JavaScript 吗?恭喜并欢迎你!

为每个图标注册渐变

在第二部分中,你在 /resources/js/const.js 文件中定义了一组自定义图标,该文件应如下所示:

export const ICONS = [
	{ value: 'floral-heart', icon: '❦' },
	{ value: 'blossom',      icon: '🌼' },
	// More icons...
];

图标选择器会根据所选图标自动为 Separator 区块设置渐变背景。这意味着每个图标都需要预先分配一个渐变预设。你可以使用 theme.json 中定义的预设,也可以使用默认 WordPress theme.json 中的预设。你只需要渐变的 slug 值。

现在,在你的 const.js 文件中为每个图标对象添加一个新的 gradient 属性,并赋予预设值:

export const ICONS = [
	{
		value: 'floral-heart',
		icon:  '❦'
	},
	{
		value:    'blossom',
		icon:     '🌼',
		gradient: 'luminous-vivid-amber-to-luminous-vivid-orange'
	},
	// ... 更多图标定义
];

上面的图标使用了 WordPress theme.json 文件中的渐变预设。如果你没有为图标定义渐变,它将回退到纯色。该颜色是你在第二部分编写的 CSS 中定义的。

编写一些实用函数

在构建实际控件之前,你需要编写几个自定义实用函数。你将把它们存储在本部分开头添加的新文件 /resources/js/utils.js 中。

首先,将依赖项添加到新的 utils.js 文件中。这些依赖项包括来自 const.js 文件的 ICONS 数组和 WordPress 的 TokenList

// Internal dependencies.
import { ICONS } from './const';

// WordPress dependencies.
import TokenList from '@wordpress/token-list';

你需要的第一个实用函数将获取分配给 Separator 区块的图标值。

在主题中实现这一点的方式与在插件中实现的方式有很大不同。

这里你正在为主题构建一个功能,因此要避免添加自定义区块属性。而且,你已经有一个 className 属性——这正是 Block Styles API 所使用的。所以使用它,并且要知道你已经遵守了 WordPress.org 主题审查指南的一个子集。

现在,将一个名为 getIconFromClassName() 的函数添加到你的 utils.js 文件中:

export const getIconFromClassName = ( className ) => {
	const list = new TokenList( className );

	const style = ICONS.find( ( option ) =>
		list.contains( `is-style-icon-${ option.value }` )
	);

	return undefined !== style ? style.value : '';
};

该函数通过将 className 变量传递给 TokenList 来创建一个 DOM 令牌类列表。然后,它搜索包含某个图标值的类。如果找到,则返回该图标的值。否则,返回一个空字符串。

你还需要一个用于更新区块 className 属性的函数,该函数不会干扰 Block Styles API,也不会擦除用户可能从 UI 输入的任何 CSS 类(提示:你可以在区块的高级选项卡下的附加 CSS 类字段中找到所有自定义类)。

现在将 updateIconClass() 函数添加到你的 utils.js 文件中:

export const updateIconClass = ( className, newIcon = '', oldIcon = '' ) => {
	const list = new TokenList( className );

	if ( oldIcon ) {
		list.remove( `is-style-icon-${ oldIcon }` );
	}

	if ( newIcon ) {
		list.add( `is-style-icon-${ newIcon }` );
	}

	return list.value;
};

同样,此函数使用 TokenList 函数创建一个类名列表。这使得定位与图标相关的类变得容易,移除旧的图标类并添加新的。

构建自定义控件

准备工作终于完成了。让我们开始构建那个控件吧!

现在打开你新创建的 /resources/js/control-icons.js 文件。像往常一样,你需要导入一些你创建的常量、函数以及一些来自 WordPress 的东西,主要是组件:

// Internal dependencies.
import { ICONS } from './const';
import { getIconFromClassName, updateIconClass } from './utils';

// WordPress dependencies.
import { __ } from '@wordpress/i18n';
import { starFilled } from '@wordpress/icons';

import {
	BaseControl,
	Button,
	Dropdown,
	ToolbarButton,
	__experimentalGrid as Grid
} from '@wordpress/components';

除了导入之外,control-icons.js 文件将包含一个函数,该函数将是其默认导出。继续添加这个:

export default ( { attributes: { className }, setAttributes } ) => {
	// Add your component code here.
};

此函数解构了 props 参数,为你提供 attributes(一个包含区块属性的对象)和 setAttributes(一个设置区块属性的函数)。attributes 参数被进一步解构,以便你直接访问 className 属性。

让我们从一段简单的代码片段开始。

你需要一个变量来存储 Separator 区块当前分配的图标。因此,将 className 属性传递给你自定义的 getIconFromClassName() 函数(你在 utils.js 中定义的那个):

// Get the current icon.
const currentIcon = getIconFromClassName( className );

现在你需要一个函数,用于在设置新图标时更新区块的属性。它将做两件事:

  • 更新区块的类,这由你在 utils.js 中定义的 updateIconClass() 函数处理。
  • 更新区块的 gradient 属性。

现在添加 onIconButtonClick() 函数的代码:

// Update the icon class and gradient.
const onIconButtonClick = ( icon ) => setAttributes( {
	className: updateIconClass(
		className,
		currentIcon === icon.value ? '' : icon.value,
		currentIcon
	),
	gradient: currentIcon === icon.value || ! icon?.gradient
		? undefined
		: icon?.gradient
} );

对于图标选择器,你需要按钮。这些按钮让你的主题用户可以选择他们想要的 Separator 区块图标。良好的开发实践原则之一是保持 DRY(不要重复自己)。与其为每个图标创建单独的按钮,不如使用 WordPress 的 <Button> 组件创建一个按钮函数。

将此代码添加到 control-icons.js 中的函数内部:

// Builds a menu item for an icon.
const iconButton = ( icon, index ) => (
	<Button
		key={ index }
		isPressed={ currentIcon === icon.value }
		className="theme-slug-sep-icons-picker__button"
		onClick={ () => onIconButtonClick( icon ) }
	>
		{ icon.icon ?? icon.value }
	</Button>
);

现在你将构建网格部分。你将使用 WordPress 的 <BaseControl> 组件作为包装元素并输出标签。

在基础控制包装器内部,你将使用一个 <div> 来输出描述,以帮助你的主题用户理解该控件的用途。你也可以省略这个。

你还将使用 <Grid> 组件来组织每个图标按钮。你将通过遍历 ICONS 数组(从 const.js 导入的那个)并将数据传递给 iconButton() 来实现。

将此代码添加到你的 control-icons.js 文件中的函数内部:

// Builds an icon picker in a 6-column grid.
const iconPicker = (
	<BaseControl
		className="theme-slug-sep-icons-picker"
		label={ __( 'Icons', 'theme-slug' ) }
	>
		<div className="theme-slug-sep-icons-picker__description">
			{ __( 'Pick an icon to super-charge your separator.', 'theme-slug' ) }
		</div>
		<Grid className="theme-slug-sep-icons-picker__grid" columns="6">
			{ ICONS.map( ( icon, index ) =>
				iconButton( icon, index )
			) }
		</Grid>
	</BaseControl>
);

现在,创建控件本身的最后一部分。因为图标选择器控件位于区块工具栏中,所以使用 WordPress 的 <Dropdown> 组件是有意义的,将上述组件代码传递给它。

因此,将此代码添加到你的 control-icons.js 文件中的函数末尾:

// Returns the dropdown menu item.
return (
	<Dropdown
		className="theme-slug-sep-icons-dropdown"
		contentClassName="theme-slug-sep-icons-popover"
		popoverProps={ {
			headerTitle: __( 'Separator Icons', 'theme-slug' ),
			variant: 'toolbar'
		} }
		renderToggle={ ( { isOpen, onToggle } ) => (
			<ToolbarButton
				className="theme-slug-sep-icons-dropdown__button"
				icon={ starFilled }
				label={ __( 'Separator Icon', 'theme-slug' ) }
				onClick={ onToggle }
				aria-expanded={ isOpen }
				isPressed={ !! currentIcon }
			/>
		) }
		renderContent={ () => iconPicker }
	/>
);

请注意两个 render*() 函数:

  • renderToggle() 使用 WordPress 的 <ToolbarButton> 组件在工具栏中渲染按钮。
  • renderContent() 渲染你在前面步骤中制作的图标选择器。

在编辑器中显示你的组件

你可能已经构建了你的图标选择器,但你仍然需要让它显示出来。这意味着你需要告诉 WordPress 它在那里——你可以通过过滤器来实现。

还记得 /resources/js/editor.js 文件吗——如果你遵循了本教程系列第二部分的所有步骤,它应该是完全空的?是时候打开它并添加你的最终 JavaScript 代码了。

这里你还需要一些导入。

首先,你需要导入刚刚创建的组件。我们将其命名为 SeparatorIconControl。你还需要 WordPress 的 BlockControls 组件addFilter() 函数

将它们添加到你的 editor.js 文件中:

// Internal dependencies.
import SeparatorIconControl from './control-icons';

// WordPress dependencies.
import { BlockControls } from '@wordpress/block-editor';
import { addFilter } from '@wordpress/hooks';

接下来,你需要为 editor.BlockEdit 过滤器钩子编写一个过滤器回调函数。这个函数将做几件事:

  • 检查当前区块是否为 Separator 区块 (core/separator)。
  • 将图标控制组件添加到区块控件中。

将此代码添加到你的 editor.js 文件中:

const withSeparatorIcons = ( BlockEdit ) => ( props ) => {
	return 'core/separator' === props.name ? (
		<>
			<BlockEdit { ...props } />
			<BlockControls group="other">
				<SeparatorIconControl
					attributes={ props.attributes }
					setAttributes={ props.setAttributes }
				/>
			</BlockControls>
		</>
	) : (
		<BlockEdit { ...props } />
	);
};

获得工作控件的最后一步是调用 addFilter()。传入钩子名称、自定义命名空间和 withSeparatorIcons 回调函数:

addFilter(
	'editor.BlockEdit',
	'theme-slug/separator-icons',
	withSeparatorIcons
);

现在在 WordPress 编辑器中创建一篇新文章或页面,并向其中添加一个 Separator 区块。然后点击工具栏中的星星图标,确保一切正常。你应该会看到一个类似这样的下拉菜单:

WordPress 文章编辑器,其中显示了一条水平线(Separator 区块),呈现为太阳 emoji。屏幕上弹出了一个图标选择器模态框,但 emoji 图标太小了。

这还不错,但还需要一点设计上的润色。让我们现在就开始吧。

美化图标选择器设计

这个过程的最后一步是对设计进行最后的润色。添加一些样式规则会大有帮助。

我选择 SCSS 而不是普通的 CSS 来完成本教程系列的原因之一是,它允许你导入变量、混合和函数(不过,也有一些构建工具可以让你在 CSS 中完成所有这些)。

因为你的组件在 WordPress 之外,你需要一种方法来访问诸如内边距和边距值之类的东西,以便为用户保持一致的体验。你可以通过导入 WordPress 的 _variables.scss 文件来获得这些。

现在打开你的 /resources/scss/editor.scss 文件并添加:

// WordPress imports.
@use '~@wordpress/base-styles/variables';

该文件有许多变量,你可以在各种项目中使用。但对于这个项目,你只需要 $grid-unit-20 来进行一些内边距和边距调整。

增加 emoji 图标本身的大小也无妨。我为此选择了 20px,但你可以随意调整这个数字。

你只剩下最后一点代码了。所以将此 SCSS 添加到 editor.scss

.theme-slug-sep-icons-popover .components-popover__content {
	padding: variables.$grid-unit-20;
}

.theme-slug-sep-icons-picker {
	&__description {
		margin: 0 0 variables.$grid-unit-20;
	}

	&__button {
		font-size: 20px;
	}
}

你的图标选择器现在应该看起来像这样:

WordPress 文章编辑器,其中显示了一条水平线(Separator 区块),呈现为太阳 emoji。屏幕上弹出了一个图标选择器模态框。

好多了,对吧?

旅程的终点

随着对设计的最后调整,本教程系列已经结束。恭喜你!

你现在可以冒险进入超越 Block Styles API 的广阔而奇妙的世界了。你已经拥有了成功所需的所有工具。

附录

本系列中有很多我没有涵盖的内容:比如更多关于我自己的开发和决策过程的见解。或者更多的技巧和窍门。

但我也想尊重你的时间。在超过 7000 字,或者说相当于一篇 28 页大学论文的长度之后,你可能已经读得够多了。是时候去构建一些很酷的东西了!

类命名

在整个系列中,我使用了 .is-style-{value} 命名约定,这是 Block Styles API 使用的。这使得将代码从第二部分迁移到第三部分变得更容易。如果你愿意,也可以轻松切换回 Block Styles API。

在我的代码中,我会使用 .has-icon-{value} 类名。这是我通常命名事物的方式。

你可以随心所欲——你不受任何命名规则束缚。

为什么要用className属性?

你可能会好奇为什么我选择用CSS类而不是自定义属性。虽然主题评审指南并未明确禁止在块中添加自定义属性,但我认为使用现有属性更符合功能与功能指南的精神,该指南定义了属于主题的功能,而非插件。className

而且,如果你的用户更改了主题,而你的设计消失了,分隔器块上的图标类并没有消失。网站所有者仍然可以用CSS来做样式。

确实,你确实需要写几个辅助函数。但代码非常简约,可能不会比添加自定义属性所需的代码重多少。

其他可建造的东西

以下是我一直在做的一些事情——如果你需要一些灵感的话:

  • 一个用于RichText块的文本阴影选择器。
  • 用于图片和后期特色图片块的渐变环/轮廓。
  • 一个列表块下拉菜单,用于选择项目符号样式(它用的代码基本和这个教程里的一样)。
  • 一个颜色变化选择器,可以为群组和其他容器型方块设置所有颜色。

我还可以列出十几个我还没机会探索的想法。而且,我也不想因为说出我想做的功能而限制你的想象力。我认为,这样对你来说更好,你应该开始自己的旅程,去发现新的事物。我唯一请求的是:和别人分享你的经历。

资源

本教程系列第三部分中使用的资源: