diff --git a/packages/block-library/src/tag-cloud/edit.js b/packages/block-library/src/tag-cloud/edit.js
index eeb568e7a89ef1..b41e47faec3699 100644
--- a/packages/block-library/src/tag-cloud/edit.js
+++ b/packages/block-library/src/tag-cloud/edit.js
@@ -4,14 +4,14 @@
import {
Flex,
FlexItem,
- PanelBody,
ToggleControl,
SelectControl,
RangeControl,
__experimentalUnitControl as UnitControl,
__experimentalUseCustomUnits as useCustomUnits,
__experimentalParseQuantityAndUnitFromRawValue as parseQuantityAndUnitFromRawValue,
- __experimentalVStack as VStack,
+ __experimentalToolsPanel as ToolsPanel,
+ __experimentalToolsPanelItem as ToolsPanelItem,
Disabled,
} from '@wordpress/components';
import { useSelect } from '@wordpress/data';
@@ -118,10 +118,25 @@ function TagCloudEdit( { attributes, setAttributes } ) {
const inspectorControls = (
-
- {
+ setAttributes( {
+ taxonomy: 'post_tag',
+ showTagCounts: false,
+ numberOfTags: 45,
+ smallestFontSize: '8pt',
+ largestFontSize: '22pt',
+ } );
+ } }
+ >
+ taxonomy !== 'post_tag' }
+ label={ __( 'Taxonomy' ) }
+ onDeselect={ () =>
+ setAttributes( { taxonomy: 'post_tag' } )
+ }
+ isShownByDefault
>
+
+
+ smallestFontSize !== '8pt' || largestFontSize !== '22pt'
+ }
+ label={ __( 'Font size' ) }
+ onDeselect={ () =>
+ setAttributes( {
+ smallestFontSize: '8pt',
+ largestFontSize: '22pt',
+ } )
+ }
+ isShownByDefault
+ >
+
+ numberOfTags !== 45 }
+ label={ __( 'Number of tags' ) }
+ onDeselect={ () => setAttributes( { numberOfTags: 45 } ) }
+ isShownByDefault
+ >
+
+ showTagCounts !== false }
+ label={ __( 'Show tag counts' ) }
+ onDeselect={ () =>
+ setAttributes( { showTagCounts: false } )
+ }
+ isShownByDefault
+ >
-
-
+
+
);
diff --git a/packages/block-library/src/tag-cloud/editor.scss b/packages/block-library/src/tag-cloud/editor.scss
index e85129e22f1aca..d00a450174f2fd 100644
--- a/packages/block-library/src/tag-cloud/editor.scss
+++ b/packages/block-library/src/tag-cloud/editor.scss
@@ -9,11 +9,3 @@
border: none;
border-radius: inherit;
}
-
-.wp-block-tag-cloud__inspector-settings {
- .components-base-control,
- .components-base-control:last-child {
- // Cancel out extra margins added by block inspector
- margin-bottom: 0;
- }
-}
diff --git a/packages/components/CHANGELOG.md b/packages/components/CHANGELOG.md
index af71c4104b4d97..c58817a420a746 100644
--- a/packages/components/CHANGELOG.md
+++ b/packages/components/CHANGELOG.md
@@ -16,6 +16,10 @@
- `BoxControl`: Better respect for the `min` prop in the Range Slider ([#67819](https://github.com/WordPress/gutenberg/pull/67819)).
+### Experimental
+
+- Add new `Badge` component ([#66555](https://github.com/WordPress/gutenberg/pull/66555)).
+
## 29.0.0 (2024-12-11)
### Breaking Changes
diff --git a/packages/components/src/badge/README.md b/packages/components/src/badge/README.md
new file mode 100644
index 00000000000000..0be531ca6f2df8
--- /dev/null
+++ b/packages/components/src/badge/README.md
@@ -0,0 +1,22 @@
+# Badge
+
+
+
+See the WordPress Storybook for more detailed, interactive documentation.
+
+## Props
+
+### `children`
+
+Text to display inside the badge.
+
+ - Type: `string`
+ - Required: Yes
+
+### `intent`
+
+Badge variant.
+
+ - Type: `"default" | "info" | "success" | "warning" | "error"`
+ - Required: No
+ - Default: `default`
diff --git a/packages/components/src/badge/docs-manifest.json b/packages/components/src/badge/docs-manifest.json
new file mode 100644
index 00000000000000..3b70c0ef228432
--- /dev/null
+++ b/packages/components/src/badge/docs-manifest.json
@@ -0,0 +1,5 @@
+{
+ "$schema": "../../schemas/docs-manifest.json",
+ "displayName": "Badge",
+ "filePath": "./index.tsx"
+}
diff --git a/packages/components/src/badge/index.tsx b/packages/components/src/badge/index.tsx
new file mode 100644
index 00000000000000..8a55f3881215f3
--- /dev/null
+++ b/packages/components/src/badge/index.tsx
@@ -0,0 +1,66 @@
+/**
+ * External dependencies
+ */
+import clsx from 'clsx';
+
+/**
+ * WordPress dependencies
+ */
+import { info, caution, error, published } from '@wordpress/icons';
+
+/**
+ * Internal dependencies
+ */
+import type { BadgeProps } from './types';
+import type { WordPressComponentProps } from '../context';
+import Icon from '../icon';
+
+function Badge( {
+ className,
+ intent = 'default',
+ children,
+ ...props
+}: WordPressComponentProps< BadgeProps, 'span', false > ) {
+ /**
+ * Returns an icon based on the badge context.
+ *
+ * @return The corresponding icon for the provided context.
+ */
+ function contextBasedIcon() {
+ switch ( intent ) {
+ case 'info':
+ return info;
+ case 'success':
+ return published;
+ case 'warning':
+ return caution;
+ case 'error':
+ return error;
+ default:
+ return null;
+ }
+ }
+
+ return (
+
+ { intent !== 'default' && (
+
+ ) }
+ { children }
+
+ );
+}
+
+export default Badge;
diff --git a/packages/components/src/badge/stories/index.story.tsx b/packages/components/src/badge/stories/index.story.tsx
new file mode 100644
index 00000000000000..aaa4bfb3c08f60
--- /dev/null
+++ b/packages/components/src/badge/stories/index.story.tsx
@@ -0,0 +1,53 @@
+/**
+ * External dependencies
+ */
+import type { Meta, StoryObj } from '@storybook/react';
+
+/**
+ * Internal dependencies
+ */
+import Badge from '..';
+
+const meta = {
+ component: Badge,
+ title: 'Components/Containers/Badge',
+ tags: [ 'status-private' ],
+} satisfies Meta< typeof Badge >;
+
+export default meta;
+
+type Story = StoryObj< typeof meta >;
+
+export const Default: Story = {
+ args: {
+ children: 'Code is Poetry',
+ },
+};
+
+export const Info: Story = {
+ args: {
+ ...Default.args,
+ intent: 'info',
+ },
+};
+
+export const Success: Story = {
+ args: {
+ ...Default.args,
+ intent: 'success',
+ },
+};
+
+export const Warning: Story = {
+ args: {
+ ...Default.args,
+ intent: 'warning',
+ },
+};
+
+export const Error: Story = {
+ args: {
+ ...Default.args,
+ intent: 'error',
+ },
+};
diff --git a/packages/components/src/badge/styles.scss b/packages/components/src/badge/styles.scss
new file mode 100644
index 00000000000000..e1e9cd5312d11a
--- /dev/null
+++ b/packages/components/src/badge/styles.scss
@@ -0,0 +1,38 @@
+$badge-colors: (
+ "info": #3858e9,
+ "warning": $alert-yellow,
+ "error": $alert-red,
+ "success": $alert-green,
+);
+
+.components-badge {
+ background-color: color-mix(in srgb, $white 90%, var(--base-color));
+ color: color-mix(in srgb, $black 50%, var(--base-color));
+ padding: 0 $grid-unit-10;
+ min-height: $grid-unit-30;
+ border-radius: $radius-small;
+ font-size: $font-size-small;
+ font-weight: 400;
+ flex-shrink: 0;
+ line-height: $font-line-height-small;
+ width: fit-content;
+ display: flex;
+ align-items: center;
+ gap: 2px;
+
+ &:where(.is-default) {
+ background-color: $gray-100;
+ color: $gray-800;
+ }
+
+ &.has-icon {
+ padding-inline-start: $grid-unit-05;
+ }
+
+ // Generate color variants
+ @each $type, $color in $badge-colors {
+ &.is-#{$type} {
+ --base-color: #{$color};
+ }
+ }
+}
diff --git a/packages/components/src/badge/test/index.tsx b/packages/components/src/badge/test/index.tsx
new file mode 100644
index 00000000000000..47c832eb3c8300
--- /dev/null
+++ b/packages/components/src/badge/test/index.tsx
@@ -0,0 +1,40 @@
+/**
+ * External dependencies
+ */
+import { render, screen } from '@testing-library/react';
+
+/**
+ * Internal dependencies
+ */
+import Badge from '..';
+
+describe( 'Badge', () => {
+ it( 'should render correctly with default props', () => {
+ render( Code is Poetry );
+ const badge = screen.getByText( 'Code is Poetry' );
+ expect( badge ).toBeInTheDocument();
+ expect( badge.tagName ).toBe( 'SPAN' );
+ expect( badge ).toHaveClass( 'components-badge' );
+ } );
+
+ it( 'should render as per its intent and contain an icon', () => {
+ render( Code is Poetry );
+ const badge = screen.getByText( 'Code is Poetry' );
+ expect( badge ).toHaveClass( 'components-badge', 'is-error' );
+ expect( badge ).toHaveClass( 'has-icon' );
+ } );
+
+ it( 'should combine custom className with default class', () => {
+ render( Code is Poetry );
+ const badge = screen.getByText( 'Code is Poetry' );
+ expect( badge ).toHaveClass( 'components-badge' );
+ expect( badge ).toHaveClass( 'custom-class' );
+ } );
+
+ it( 'should pass through additional props', () => {
+ render( Code is Poetry );
+ const badge = screen.getByTestId( 'custom-badge' );
+ expect( badge ).toHaveTextContent( 'Code is Poetry' );
+ expect( badge ).toHaveClass( 'components-badge' );
+ } );
+} );
diff --git a/packages/components/src/badge/types.ts b/packages/components/src/badge/types.ts
new file mode 100644
index 00000000000000..91cd7c39b549bb
--- /dev/null
+++ b/packages/components/src/badge/types.ts
@@ -0,0 +1,12 @@
+export type BadgeProps = {
+ /**
+ * Badge variant.
+ *
+ * @default 'default'
+ */
+ intent?: 'default' | 'info' | 'success' | 'warning' | 'error';
+ /**
+ * Text to display inside the badge.
+ */
+ children: string;
+};
diff --git a/packages/components/src/private-apis.ts b/packages/components/src/private-apis.ts
index 2ced100dc576be..f5a9ee90519c2d 100644
--- a/packages/components/src/private-apis.ts
+++ b/packages/components/src/private-apis.ts
@@ -8,6 +8,7 @@ import Theme from './theme';
import { Tabs } from './tabs';
import { kebabCase } from './utils/strings';
import { lock } from './lock-unlock';
+import Badge from './badge';
export const privateApis = {};
lock( privateApis, {
@@ -17,4 +18,5 @@ lock( privateApis, {
Theme,
Menu,
kebabCase,
+ Badge,
} );
diff --git a/packages/components/src/style.scss b/packages/components/src/style.scss
index 70317f4a2d0e0b..368dec0f5e253d 100644
--- a/packages/components/src/style.scss
+++ b/packages/components/src/style.scss
@@ -10,6 +10,7 @@
// Components
@import "./animate/style.scss";
@import "./autocomplete/style.scss";
+@import "./badge/styles.scss";
@import "./button-group/style.scss";
@import "./button/style.scss";
@import "./checkbox-control/style.scss";
diff --git a/packages/edit-site/src/components/sidebar-dataviews/custom-dataviews-list.js b/packages/edit-site/src/components/sidebar-dataviews/custom-dataviews-list.js
index 847029e8d6dcfe..467648e814276d 100644
--- a/packages/edit-site/src/components/sidebar-dataviews/custom-dataviews-list.js
+++ b/packages/edit-site/src/components/sidebar-dataviews/custom-dataviews-list.js
@@ -212,7 +212,7 @@ export default function CustomDataViewsList( { type, activeView, isCustom } ) {
{ __( 'Custom Views' ) }
-
+
{ customDataViews.map( ( customViewRecord ) => {
return (
diff --git a/packages/editor/src/components/post-actions/actions.js b/packages/editor/src/components/post-actions/actions.js
index 808134ea969a11..023b93d31bb511 100644
--- a/packages/editor/src/components/post-actions/actions.js
+++ b/packages/editor/src/components/post-actions/actions.js
@@ -11,6 +11,7 @@ import { store as coreStore } from '@wordpress/core-data';
import { store as editorStore } from '../../store';
import { unlock } from '../../lock-unlock';
import { useSetAsHomepageAction } from './set-as-homepage';
+import { useSetAsPostsPageAction } from './set-as-posts-page';
export function usePostActions( { postType, onActionPerformed, context } ) {
const { defaultActions } = useSelect(
@@ -43,7 +44,8 @@ export function usePostActions( { postType, onActionPerformed, context } ) {
);
const setAsHomepageAction = useSetAsHomepageAction();
- const shouldShowSetAsHomepageAction =
+ const setAsPostsPageAction = useSetAsPostsPageAction();
+ const shouldShowHomepageActions =
canManageOptions && ! hasFrontPageTemplate;
const { registerPostTypeSchema } = unlock( useDispatch( editorStore ) );
@@ -53,10 +55,15 @@ export function usePostActions( { postType, onActionPerformed, context } ) {
return useMemo( () => {
let actions = [ ...defaultActions ];
- if ( shouldShowSetAsHomepageAction ) {
- actions.push( setAsHomepageAction );
+ if ( shouldShowHomepageActions ) {
+ actions.push( setAsHomepageAction, setAsPostsPageAction );
}
+ // Ensure "Move to trash" is always the last action.
+ actions = actions.sort( ( a, b ) =>
+ b.id === 'move-to-trash' ? -1 : 0
+ );
+
// Filter actions based on provided context. If not provided
// all actions are returned. We'll have a single entry for getting the actions
// and the consumer should provide the context to filter the actions, if needed.
@@ -123,6 +130,7 @@ export function usePostActions( { postType, onActionPerformed, context } ) {
defaultActions,
onActionPerformed,
setAsHomepageAction,
- shouldShowSetAsHomepageAction,
+ setAsPostsPageAction,
+ shouldShowHomepageActions,
] );
}
diff --git a/packages/editor/src/components/post-actions/set-as-homepage.js b/packages/editor/src/components/post-actions/set-as-homepage.js
index 0252c84e3ab3ff..671906575b4123 100644
--- a/packages/editor/src/components/post-actions/set-as-homepage.js
+++ b/packages/editor/src/components/post-actions/set-as-homepage.js
@@ -12,20 +12,11 @@ import {
import { useDispatch, useSelect } from '@wordpress/data';
import { store as coreStore } from '@wordpress/core-data';
import { store as noticesStore } from '@wordpress/notices';
-import { decodeEntities } from '@wordpress/html-entities';
-const getItemTitle = ( item ) => {
- if ( typeof item.title === 'string' ) {
- return decodeEntities( item.title );
- }
- if ( item.title && 'rendered' in item.title ) {
- return decodeEntities( item.title.rendered );
- }
- if ( item.title && 'raw' in item.title ) {
- return decodeEntities( item.title.raw );
- }
- return '';
-};
+/**
+ * Internal dependencies
+ */
+import { getItemTitle } from '../../utils/get-item-title';
const SetAsHomepageModal = ( { items, closeModal } ) => {
const [ item ] = items;
@@ -48,8 +39,7 @@ const SetAsHomepageModal = ( { items, closeModal } ) => {
}
);
- const { saveEditedEntityRecord, saveEntityRecord } =
- useDispatch( coreStore );
+ const { saveEntityRecord } = useDispatch( coreStore );
const { createSuccessNotice, createErrorNotice } =
useDispatch( noticesStore );
@@ -57,29 +47,19 @@ const SetAsHomepageModal = ( { items, closeModal } ) => {
event.preventDefault();
try {
- // Save new home page settings.
- await saveEditedEntityRecord( 'root', 'site', undefined, {
- page_on_front: item.id,
- show_on_front: 'page',
- } );
-
- // This second call to a save function is a workaround for a bug in
- // `saveEditedEntityRecord`. This forces the root site settings to be updated.
- // See https://github.com/WordPress/gutenberg/issues/67161.
await saveEntityRecord( 'root', 'site', {
page_on_front: item.id,
show_on_front: 'page',
} );
- createSuccessNotice( __( 'Homepage updated' ), {
+ createSuccessNotice( __( 'Homepage updated.' ), {
type: 'snackbar',
} );
} catch ( error ) {
- const typedError = error;
const errorMessage =
- typedError.message && typedError.code !== 'unknown_error'
- ? typedError.message
- : __( 'An error occurred while setting the homepage' );
+ error.message && error.code !== 'unknown_error'
+ ? error.message
+ : __( 'An error occurred while setting the homepage.' );
createErrorNotice( errorMessage, { type: 'snackbar' } );
} finally {
closeModal?.();
diff --git a/packages/editor/src/components/post-actions/set-as-posts-page.js b/packages/editor/src/components/post-actions/set-as-posts-page.js
new file mode 100644
index 00000000000000..67c42a7991fe45
--- /dev/null
+++ b/packages/editor/src/components/post-actions/set-as-posts-page.js
@@ -0,0 +1,158 @@
+/**
+ * WordPress dependencies
+ */
+import { __, sprintf } from '@wordpress/i18n';
+import { useMemo } from '@wordpress/element';
+import {
+ Button,
+ __experimentalText as Text,
+ __experimentalHStack as HStack,
+ __experimentalVStack as VStack,
+} from '@wordpress/components';
+import { useDispatch, useSelect } from '@wordpress/data';
+import { store as coreStore } from '@wordpress/core-data';
+import { store as noticesStore } from '@wordpress/notices';
+
+/**
+ * Internal dependencies
+ */
+import { getItemTitle } from '../../utils/get-item-title';
+
+const SetAsPostsPageModal = ( { items, closeModal } ) => {
+ const [ item ] = items;
+ const pageTitle = getItemTitle( item );
+ const { currentPostsPage, isPageForPostsSet, isSaving } = useSelect(
+ ( select ) => {
+ const { getEntityRecord, isSavingEntityRecord } =
+ select( coreStore );
+ const siteSettings = getEntityRecord( 'root', 'site' );
+ const currentPostsPageItem = getEntityRecord(
+ 'postType',
+ 'page',
+ siteSettings?.page_for_posts
+ );
+ return {
+ currentPostsPage: currentPostsPageItem,
+ isPageForPostsSet: siteSettings?.page_for_posts !== 0,
+ isSaving: isSavingEntityRecord( 'root', 'site' ),
+ };
+ }
+ );
+
+ const { saveEntityRecord } = useDispatch( coreStore );
+ const { createSuccessNotice, createErrorNotice } =
+ useDispatch( noticesStore );
+
+ async function onSetPageAsPostsPage( event ) {
+ event.preventDefault();
+
+ try {
+ await saveEntityRecord( 'root', 'site', {
+ page_for_posts: item.id,
+ show_on_front: 'page',
+ } );
+
+ createSuccessNotice( __( 'Posts page updated.' ), {
+ type: 'snackbar',
+ } );
+ } catch ( error ) {
+ const errorMessage =
+ error.message && error.code !== 'unknown_error'
+ ? error.message
+ : __( 'An error occurred while setting the posts page.' );
+ createErrorNotice( errorMessage, { type: 'snackbar' } );
+ } finally {
+ closeModal?.();
+ }
+ }
+
+ const modalWarning =
+ isPageForPostsSet && currentPostsPage
+ ? sprintf(
+ // translators: %s: title of the current posts page.
+ __( 'This will replace the current posts page: "%s"' ),
+ getItemTitle( currentPostsPage )
+ )
+ : __( 'This page will show the latest posts.' );
+
+ const modalText = sprintf(
+ // translators: %1$s: title of the page to be set as the posts page, %2$s: posts page replacement warning message.
+ __( 'Set "%1$s" as the posts page? %2$s' ),
+ pageTitle,
+ modalWarning
+ );
+
+ // translators: Button label to confirm setting the specified page as the posts page.
+ const modalButtonLabel = __( 'Set posts page' );
+
+ return (
+
+ );
+};
+
+export const useSetAsPostsPageAction = () => {
+ const { pageOnFront, pageForPosts } = useSelect( ( select ) => {
+ const { getEntityRecord } = select( coreStore );
+ const siteSettings = getEntityRecord( 'root', 'site' );
+ return {
+ pageOnFront: siteSettings?.page_on_front,
+ pageForPosts: siteSettings?.page_for_posts,
+ };
+ } );
+
+ return useMemo(
+ () => ( {
+ id: 'set-as-posts-page',
+ label: __( 'Set as posts page' ),
+ isEligible( post ) {
+ if ( post.status !== 'publish' ) {
+ return false;
+ }
+
+ if ( post.type !== 'page' ) {
+ return false;
+ }
+
+ // Don't show the action if the page is already set as the homepage.
+ if ( pageOnFront === post.id ) {
+ return false;
+ }
+
+ // Don't show the action if the page is already set as the page for posts.
+ if ( pageForPosts === post.id ) {
+ return false;
+ }
+
+ return true;
+ },
+ RenderModal: SetAsPostsPageModal,
+ } ),
+ [ pageForPosts, pageOnFront ]
+ );
+};
diff --git a/packages/editor/src/utils/get-item-title.js b/packages/editor/src/utils/get-item-title.js
new file mode 100644
index 00000000000000..86929c27408a81
--- /dev/null
+++ b/packages/editor/src/utils/get-item-title.js
@@ -0,0 +1,25 @@
+/**
+ * WordPress dependencies
+ */
+import { decodeEntities } from '@wordpress/html-entities';
+
+/**
+ * Helper function to get the title of a post item.
+ * This is duplicated from the `@wordpress/fields` package.
+ * `packages/fields/src/actions/utils.ts`
+ *
+ * @param {Object} item The post item.
+ * @return {string} The title of the item, or an empty string if the title is not found.
+ */
+export function getItemTitle( item ) {
+ if ( typeof item.title === 'string' ) {
+ return decodeEntities( item.title );
+ }
+ if ( item.title && 'rendered' in item.title ) {
+ return decodeEntities( item.title.rendered );
+ }
+ if ( item.title && 'raw' in item.title ) {
+ return decodeEntities( item.title.raw );
+ }
+ return '';
+}
diff --git a/packages/icons/CHANGELOG.md b/packages/icons/CHANGELOG.md
index d622019f1ee783..64c1a58b549caf 100644
--- a/packages/icons/CHANGELOG.md
+++ b/packages/icons/CHANGELOG.md
@@ -2,6 +2,10 @@
## Unreleased
+- Add new `caution` icon ([#66555](https://github.com/WordPress/gutenberg/pull/66555)).
+- Add new `error` icon ([#66555](https://github.com/WordPress/gutenberg/pull/66555)).
+- Deprecate `warning` icon and rename to `cautionFilled` ([#67895](https://github.com/WordPress/gutenberg/pull/67895)).
+
## 10.14.0 (2024-12-11)
## 10.13.0 (2024-11-27)
diff --git a/packages/icons/src/icon/stories/index.story.js b/packages/icons/src/icon/stories/index.story.js
index 8cbf65d9f259e9..406f986e6ef5dc 100644
--- a/packages/icons/src/icon/stories/index.story.js
+++ b/packages/icons/src/icon/stories/index.story.js
@@ -11,7 +11,14 @@ import check from '../../library/check';
import * as icons from '../../';
import keywords from './keywords';
-const { Icon: _Icon, ...availableIcons } = icons;
+const {
+ Icon: _Icon,
+
+ // Deprecated aliases
+ warning: _warning,
+
+ ...availableIcons
+} = icons;
const meta = {
component: Icon,
diff --git a/packages/icons/src/icon/stories/keywords.ts b/packages/icons/src/icon/stories/keywords.ts
index 3fd962e047bc1d..4de5ae9a7dae93 100644
--- a/packages/icons/src/icon/stories/keywords.ts
+++ b/packages/icons/src/icon/stories/keywords.ts
@@ -1,13 +1,15 @@
const keywords: Partial< Record< keyof typeof import('../../'), string[] > > = {
cancelCircleFilled: [ 'close' ],
+ caution: [ 'alert', 'warning' ],
+ cautionFilled: [ 'alert', 'warning' ],
create: [ 'add' ],
+ error: [ 'alert', 'caution', 'warning' ],
file: [ 'folder' ],
seen: [ 'show' ],
thumbsDown: [ 'dislike' ],
thumbsUp: [ 'like' ],
trash: [ 'delete' ],
unseen: [ 'hide' ],
- warning: [ 'alert', 'caution' ],
};
export default keywords;
diff --git a/packages/icons/src/index.js b/packages/icons/src/index.js
index 14eaec92b78c4d..e82b09e5d5afe9 100644
--- a/packages/icons/src/index.js
+++ b/packages/icons/src/index.js
@@ -37,6 +37,12 @@ export { default as caption } from './library/caption';
export { default as capturePhoto } from './library/capture-photo';
export { default as captureVideo } from './library/capture-video';
export { default as category } from './library/category';
+export { default as caution } from './library/caution';
+export {
+ /** @deprecated Import `cautionFilled` instead. */
+ default as warning,
+ default as cautionFilled,
+} from './library/caution-filled';
export { default as chartBar } from './library/chart-bar';
export { default as check } from './library/check';
export { default as chevronDown } from './library/chevron-down';
@@ -84,6 +90,7 @@ export { default as download } from './library/download';
export { default as edit } from './library/edit';
export { default as envelope } from './library/envelope';
export { default as external } from './library/external';
+export { default as error } from './library/error';
export { default as file } from './library/file';
export { default as filter } from './library/filter';
export { default as flipHorizontal } from './library/flip-horizontal';
@@ -301,6 +308,5 @@ export { default as update } from './library/update';
export { default as upload } from './library/upload';
export { default as verse } from './library/verse';
export { default as video } from './library/video';
-export { default as warning } from './library/warning';
export { default as widget } from './library/widget';
export { default as wordpress } from './library/wordpress';
diff --git a/packages/icons/src/library/caution-filled.js b/packages/icons/src/library/caution-filled.js
new file mode 100644
index 00000000000000..5e7779db85f862
--- /dev/null
+++ b/packages/icons/src/library/caution-filled.js
@@ -0,0 +1,12 @@
+/**
+ * WordPress dependencies
+ */
+import { SVG, Path } from '@wordpress/primitives';
+
+const cautionFilled = (
+
+);
+
+export default cautionFilled;
diff --git a/packages/icons/src/library/caution.js b/packages/icons/src/library/caution.js
new file mode 100644
index 00000000000000..f6d23fdfc7eddf
--- /dev/null
+++ b/packages/icons/src/library/caution.js
@@ -0,0 +1,16 @@
+/**
+ * WordPress dependencies
+ */
+import { SVG, Path } from '@wordpress/primitives';
+
+const caution = (
+
+);
+
+export default caution;
diff --git a/packages/icons/src/library/error.js b/packages/icons/src/library/error.js
new file mode 100644
index 00000000000000..2dc2bccbf639ce
--- /dev/null
+++ b/packages/icons/src/library/error.js
@@ -0,0 +1,16 @@
+/**
+ * WordPress dependencies
+ */
+import { SVG, Path } from '@wordpress/primitives';
+
+const error = (
+
+);
+
+export default error;
diff --git a/packages/icons/src/library/info.js b/packages/icons/src/library/info.js
index f3425d9e950415..24d41d798263f7 100644
--- a/packages/icons/src/library/info.js
+++ b/packages/icons/src/library/info.js
@@ -4,8 +4,12 @@
import { SVG, Path } from '@wordpress/primitives';
const info = (
-