From e3f26450a6ab2d7079b05fb91dd08dd121851f46 Mon Sep 17 00:00:00 2001 From: macayu17 Date: Sun, 24 May 2026 23:43:16 +0530 Subject: [PATCH 1/8] Button.Icon: Fix clipped icons --- packages/ui/src/button/icon.tsx | 8 +++++--- packages/ui/src/button/style.module.css | 4 ++++ packages/ui/src/button/test/icon.test.tsx | 16 +++++++++++++++- 3 files changed, 24 insertions(+), 4 deletions(-) diff --git a/packages/ui/src/button/icon.tsx b/packages/ui/src/button/icon.tsx index 45da9dc1dc03c5..21b348d2250e69 100644 --- a/packages/ui/src/button/icon.tsx +++ b/packages/ui/src/button/icon.tsx @@ -1,15 +1,17 @@ +import clsx from 'clsx'; import { forwardRef } from '@wordpress/element'; import { type ButtonIconProps } from './types'; import { Icon } from '../icon'; +import styles from './style.module.css'; export const ButtonIcon = forwardRef< SVGSVGElement, ButtonIconProps >( - function ButtonIcon( { icon, ...props }, ref ) { + function ButtonIcon( { className, icon, size = 24, ...props }, ref ) { return ( ); diff --git a/packages/ui/src/button/style.module.css b/packages/ui/src/button/style.module.css index d62e57bd43eb9a..39ebc9405443b6 100644 --- a/packages/ui/src/button/style.module.css +++ b/packages/ui/src/button/style.module.css @@ -142,6 +142,10 @@ --wp-ui-button-height: 24px; } + .icon { + margin: calc(-1 * var(--wpds-dimension-base)); + } + /* Variants (`tone` + `variant`) overrides compared to default styles */ /* All outline buttons have a border */ diff --git a/packages/ui/src/button/test/icon.test.tsx b/packages/ui/src/button/test/icon.test.tsx index 97283b06fbe590..aa15af180c312e 100644 --- a/packages/ui/src/button/test/icon.test.tsx +++ b/packages/ui/src/button/test/icon.test.tsx @@ -1,5 +1,5 @@ import { createRef } from '@wordpress/element'; -import { render } from '@testing-library/react'; +import { render, screen } from '@testing-library/react'; import { ButtonIcon } from '../icon'; describe( 'Button.Icon', () => { @@ -10,4 +10,18 @@ describe( 'Button.Icon', () => { expect( ref.current ).toBeInstanceOf( SVGSVGElement ); } ); + + it( 'preserves the icon viewBox', () => { + render( + } + /> + ); + + expect( screen.getByTestId( 'button-icon' ) ).toHaveAttribute( + 'viewBox', + '-2 -2 24 24' + ); + } ); } ); From a5ee51365c28dc8719fb07a64b6bd32652c1cbf4 Mon Sep 17 00:00:00 2001 From: macayu17 Date: Sun, 24 May 2026 23:49:19 +0530 Subject: [PATCH 2/8] UI: Add Button.Icon changelog entry --- packages/ui/CHANGELOG.md | 1 + 1 file changed, 1 insertion(+) diff --git a/packages/ui/CHANGELOG.md b/packages/ui/CHANGELOG.md index 6f1a359de25515..ffa7765104f1e1 100644 --- a/packages/ui/CHANGELOG.md +++ b/packages/ui/CHANGELOG.md @@ -16,6 +16,7 @@ ### Bug Fixes +- `Button.Icon`: Preserve icon view boxes so icons with non-standard `viewBox` values are not clipped ([#78614](https://github.com/WordPress/gutenberg/pull/78614)). - `IconButton`: Default `focusableWhenDisabled` to `true`, matching `Button` ([#78526](https://github.com/WordPress/gutenberg/pull/78526)). - `Button`: Do not show the interactive cursor when disabled ([#78479](https://github.com/WordPress/gutenberg/pull/78479)). - `Autocomplete`: Fix the TypeScript prop types for the `Root` and `Value` primitives ([#78450](https://github.com/WordPress/gutenberg/pull/78450)). From 445e7f545b7b5f0a5b4435e97c0d2b2b065adbc3 Mon Sep 17 00:00:00 2001 From: macayu17 Date: Mon, 25 May 2026 19:17:32 +0530 Subject: [PATCH 3/8] fix: address Button.Icon review --- packages/ui/src/button/icon.tsx | 6 +++--- packages/ui/src/button/style.module.css | 5 ++++- packages/ui/src/button/test/icon.test.tsx | 5 +++-- 3 files changed, 10 insertions(+), 6 deletions(-) diff --git a/packages/ui/src/button/icon.tsx b/packages/ui/src/button/icon.tsx index 21b348d2250e69..9a491d85299932 100644 --- a/packages/ui/src/button/icon.tsx +++ b/packages/ui/src/button/icon.tsx @@ -5,13 +5,13 @@ import { Icon } from '../icon'; import styles from './style.module.css'; export const ButtonIcon = forwardRef< SVGSVGElement, ButtonIconProps >( - function ButtonIcon( { className, icon, size = 24, ...props }, ref ) { + function ButtonIcon( { className, icon, ...props }, ref ) { return ( ); diff --git a/packages/ui/src/button/style.module.css b/packages/ui/src/button/style.module.css index 39ebc9405443b6..c707adbe05f031 100644 --- a/packages/ui/src/button/style.module.css +++ b/packages/ui/src/button/style.module.css @@ -23,6 +23,9 @@ --wp-ui-button-aspect-ratio: auto; /* Useful for overrides such as icon buttons */ --wp-ui-button-font-size: var(--wpds-typography-font-size-md); --wp-ui-button-min-width: calc(4ch + 2 * var(--wp-ui-button-padding-inline)); + /* Icons are rendered as 24px but need to occupy a 16px footprint */ + /* TODO: replace hardcoded values with DS size tokens once available */ + --wp-ui-button-icon-margin: calc((24px - 16px) / 2); /* by default, borders have the same color as the background */ --wp-ui-button-border-color: var(--wp-ui-button-background-color); @@ -143,7 +146,7 @@ } .icon { - margin: calc(-1 * var(--wpds-dimension-base)); + margin: calc(-1 * var(--wp-ui-button-icon-margin, 0px)); } /* Variants (`tone` + `variant`) overrides compared to default styles */ diff --git a/packages/ui/src/button/test/icon.test.tsx b/packages/ui/src/button/test/icon.test.tsx index aa15af180c312e..3ab2e9fde66bde 100644 --- a/packages/ui/src/button/test/icon.test.tsx +++ b/packages/ui/src/button/test/icon.test.tsx @@ -14,12 +14,13 @@ describe( 'Button.Icon', () => { it( 'preserves the icon viewBox', () => { render( } /> ); - expect( screen.getByTestId( 'button-icon' ) ).toHaveAttribute( + expect( screen.getByRole( 'img', { hidden: true } ) ).toHaveAttribute( 'viewBox', '-2 -2 24 24' ); From bf4771debeaad6a289038383d89053d58fdf299b Mon Sep 17 00:00:00 2001 From: macayu17 Date: Mon, 25 May 2026 23:56:02 +0530 Subject: [PATCH 4/8] Button.Icon: Add icon library story --- .../ui/src/button/stories/index.story.tsx | 183 +++++++++++++++++- packages/ui/src/button/test/icon.test.tsx | 17 +- 2 files changed, 182 insertions(+), 18 deletions(-) diff --git a/packages/ui/src/button/stories/index.story.tsx b/packages/ui/src/button/stories/index.story.tsx index 1d7cd1e78cba9f..9b7137b0079a52 100644 --- a/packages/ui/src/button/stories/index.story.tsx +++ b/packages/ui/src/button/stories/index.story.tsx @@ -1,7 +1,154 @@ import { Fragment } from '@wordpress/element'; import type { Meta, StoryObj } from '@storybook/react-vite'; -import { cog } from '@wordpress/icons'; +import * as icons from '@wordpress/icons'; import { Button } from '../index'; +import { Icon } from '../../icon'; +import type { IconProps } from '../../icon/types'; + +type IconLibrary = Omit< typeof icons, 'Icon' >; +type IconSlug = Extract< keyof IconLibrary, string >; + +const iconLibrary = Object.fromEntries( + Object.entries( icons ).filter( ( [ slug ] ) => slug !== 'Icon' ) +) as IconLibrary; + +const allIconEntries = Object.entries( iconLibrary ).sort( + ( [ firstSlug ], [ secondSlug ] ) => firstSlug.localeCompare( secondSlug ) +) as Array< [ IconSlug, IconProps[ 'icon' ] ] >; + +// Mirrors the affected-icon set from the linked issue demo. +const flaggedIconSlugs = [ + 'keyboardClose', + 'pages', + 'copy', + 'gallery', + 'wordpress', + 'insertBefore', + 'capturePhoto', + 'classic', + 'captureVideo', + 'blockTable', + 'calendar', + 'cover', + 'grid', + 'image', + 'media', + 'postDate', + 'tableColumnAfter', + 'tableColumnBefore', + 'tableColumnDelete', + 'tableRowAfter', + 'tableRowBefore', + 'tableRowDelete', + 'table', + 'video', + 'addTemplate', + 'commentContent', + 'store', + 'addCard', + 'box', + 'insertAfter', + 'commentAuthorName', + 'comment', + 'flipHorizontal', + 'flipVertical', + 'caption', + 'drawerLeft', + 'drawerRight', + 'footer', + 'header', + 'inbox', + 'layout', + 'overlayText', + 'postCommentsCount', + 'postCommentsForm', + 'postList', + 'preformatted', + 'sidebar', + 'archive', + 'file', + 'offline', + 'formatRTL', + 'navigationOverlay', + 'postFeaturedImage', + 'pullquote', + 'shortcode', + 'widget', + 'atSymbol', + 'commentEditLink', + 'commentReplyLink', + 'copySmall', + 'formatLTR', + 'homeButton', + 'postCategories', + 'postComments', + 'removeSubmenu', + 'timeToRead', + 'addSubmenu', + 'html', +] as const satisfies readonly IconSlug[]; + +const iconStorySectionStyle = { + display: 'grid', + gap: '1rem', + color: 'var(--wpds-color-fg-content-neutral)', + fontSize: 'var(--wpds-typography-font-size-sm)', +} satisfies React.CSSProperties; + +const iconStorySubsectionStyle = { + display: 'grid', + gap: '0.75rem', +} satisfies React.CSSProperties; + +const iconStoryHeaderStyle = { + color: 'var(--wpds-color-fg-content-neutral-weak)', + fontSize: 'var(--wpds-typography-font-size-xs)', + fontWeight: 500, + textTransform: 'uppercase' as const, +} satisfies React.CSSProperties; + +const flaggedIconGridStyle = { + display: 'grid', + gridTemplateColumns: 'minmax(12rem, max-content) max-content max-content', + gap: '12px 24px', + alignItems: 'center', +} satisfies React.CSSProperties; + +const allIconsGridStyle = { + display: 'grid', + gridTemplateColumns: 'repeat(auto-fill, minmax(180px, 1fr))', + gap: '12px', +} satisfies React.CSSProperties; + +function FlaggedIconRow( { slug }: { slug: IconSlug } ) { + const icon = iconLibrary[ slug ]; + + return ( + +
{ slug }
+ + +
+ ); +} + +function ButtonIconSample( { + icon, + slug, +}: { + icon: IconProps[ 'icon' ]; + slug: IconSlug; +} ) { + return ( + + ); +} const meta: Meta< typeof Button > = { title: 'Design System/Components/Button', @@ -158,13 +305,45 @@ export const WithIcon: Story = { ...Default.args, children: ( <> - + Button ), }, }; +export const IconLibrary: Story = { + render: () => ( +
+
+

Flagged icons

+
+
+
+ Icon (native viewBox) +
+
Button.Icon
+ { flaggedIconSlugs.map( ( slug ) => ( + + ) ) } +
+
+
+

All icons

+
+ { allIconEntries.map( ( [ slug, icon ] ) => ( + + ) ) } +
+
+
+ ), +}; + export const Loading: Story = { ...Default, args: { diff --git a/packages/ui/src/button/test/icon.test.tsx b/packages/ui/src/button/test/icon.test.tsx index 3ab2e9fde66bde..97283b06fbe590 100644 --- a/packages/ui/src/button/test/icon.test.tsx +++ b/packages/ui/src/button/test/icon.test.tsx @@ -1,5 +1,5 @@ import { createRef } from '@wordpress/element'; -import { render, screen } from '@testing-library/react'; +import { render } from '@testing-library/react'; import { ButtonIcon } from '../icon'; describe( 'Button.Icon', () => { @@ -10,19 +10,4 @@ describe( 'Button.Icon', () => { expect( ref.current ).toBeInstanceOf( SVGSVGElement ); } ); - - it( 'preserves the icon viewBox', () => { - render( - } - /> - ); - - expect( screen.getByRole( 'img', { hidden: true } ) ).toHaveAttribute( - 'viewBox', - '-2 -2 24 24' - ); - } ); } ); From 50ba24d6a20ab7c744b15a58b1c86c747b3f5b59 Mon Sep 17 00:00:00 2001 From: Lena Morita Date: Sat, 30 May 2026 04:20:29 +0900 Subject: [PATCH 5/8] Remove unnecessary CSS variable fallback --- packages/ui/src/button/style.module.css | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/ui/src/button/style.module.css b/packages/ui/src/button/style.module.css index ac66bbd1bb9c64..0e4230a98e4ac7 100644 --- a/packages/ui/src/button/style.module.css +++ b/packages/ui/src/button/style.module.css @@ -146,7 +146,7 @@ } .icon { - margin: calc(-1 * var(--wp-ui-button-icon-margin, 0px)); + margin: calc(-1 * var(--wp-ui-button-icon-margin)); } /* Variants (`tone` + `variant`) overrides compared to default styles */ From 0ed0b427639e29f15fc93819ebd2e285a9178f87 Mon Sep 17 00:00:00 2001 From: Lena Morita Date: Sat, 30 May 2026 04:20:44 +0900 Subject: [PATCH 6/8] Remove test story, change icon to `wordpress` --- .../ui/src/button/stories/index.story.tsx | 183 +----------------- 1 file changed, 2 insertions(+), 181 deletions(-) diff --git a/packages/ui/src/button/stories/index.story.tsx b/packages/ui/src/button/stories/index.story.tsx index 9b7137b0079a52..b9b7c8476eadc5 100644 --- a/packages/ui/src/button/stories/index.story.tsx +++ b/packages/ui/src/button/stories/index.story.tsx @@ -1,154 +1,7 @@ import { Fragment } from '@wordpress/element'; import type { Meta, StoryObj } from '@storybook/react-vite'; -import * as icons from '@wordpress/icons'; +import { wordpress } from '@wordpress/icons'; import { Button } from '../index'; -import { Icon } from '../../icon'; -import type { IconProps } from '../../icon/types'; - -type IconLibrary = Omit< typeof icons, 'Icon' >; -type IconSlug = Extract< keyof IconLibrary, string >; - -const iconLibrary = Object.fromEntries( - Object.entries( icons ).filter( ( [ slug ] ) => slug !== 'Icon' ) -) as IconLibrary; - -const allIconEntries = Object.entries( iconLibrary ).sort( - ( [ firstSlug ], [ secondSlug ] ) => firstSlug.localeCompare( secondSlug ) -) as Array< [ IconSlug, IconProps[ 'icon' ] ] >; - -// Mirrors the affected-icon set from the linked issue demo. -const flaggedIconSlugs = [ - 'keyboardClose', - 'pages', - 'copy', - 'gallery', - 'wordpress', - 'insertBefore', - 'capturePhoto', - 'classic', - 'captureVideo', - 'blockTable', - 'calendar', - 'cover', - 'grid', - 'image', - 'media', - 'postDate', - 'tableColumnAfter', - 'tableColumnBefore', - 'tableColumnDelete', - 'tableRowAfter', - 'tableRowBefore', - 'tableRowDelete', - 'table', - 'video', - 'addTemplate', - 'commentContent', - 'store', - 'addCard', - 'box', - 'insertAfter', - 'commentAuthorName', - 'comment', - 'flipHorizontal', - 'flipVertical', - 'caption', - 'drawerLeft', - 'drawerRight', - 'footer', - 'header', - 'inbox', - 'layout', - 'overlayText', - 'postCommentsCount', - 'postCommentsForm', - 'postList', - 'preformatted', - 'sidebar', - 'archive', - 'file', - 'offline', - 'formatRTL', - 'navigationOverlay', - 'postFeaturedImage', - 'pullquote', - 'shortcode', - 'widget', - 'atSymbol', - 'commentEditLink', - 'commentReplyLink', - 'copySmall', - 'formatLTR', - 'homeButton', - 'postCategories', - 'postComments', - 'removeSubmenu', - 'timeToRead', - 'addSubmenu', - 'html', -] as const satisfies readonly IconSlug[]; - -const iconStorySectionStyle = { - display: 'grid', - gap: '1rem', - color: 'var(--wpds-color-fg-content-neutral)', - fontSize: 'var(--wpds-typography-font-size-sm)', -} satisfies React.CSSProperties; - -const iconStorySubsectionStyle = { - display: 'grid', - gap: '0.75rem', -} satisfies React.CSSProperties; - -const iconStoryHeaderStyle = { - color: 'var(--wpds-color-fg-content-neutral-weak)', - fontSize: 'var(--wpds-typography-font-size-xs)', - fontWeight: 500, - textTransform: 'uppercase' as const, -} satisfies React.CSSProperties; - -const flaggedIconGridStyle = { - display: 'grid', - gridTemplateColumns: 'minmax(12rem, max-content) max-content max-content', - gap: '12px 24px', - alignItems: 'center', -} satisfies React.CSSProperties; - -const allIconsGridStyle = { - display: 'grid', - gridTemplateColumns: 'repeat(auto-fill, minmax(180px, 1fr))', - gap: '12px', -} satisfies React.CSSProperties; - -function FlaggedIconRow( { slug }: { slug: IconSlug } ) { - const icon = iconLibrary[ slug ]; - - return ( - -
{ slug }
- - -
- ); -} - -function ButtonIconSample( { - icon, - slug, -}: { - icon: IconProps[ 'icon' ]; - slug: IconSlug; -} ) { - return ( - - ); -} const meta: Meta< typeof Button > = { title: 'Design System/Components/Button', @@ -305,45 +158,13 @@ export const WithIcon: Story = { ...Default.args, children: ( <> - + Button ), }, }; -export const IconLibrary: Story = { - render: () => ( -
-
-

Flagged icons

-
-
-
- Icon (native viewBox) -
-
Button.Icon
- { flaggedIconSlugs.map( ( slug ) => ( - - ) ) } -
-
-
-

All icons

-
- { allIconEntries.map( ( [ slug, icon ] ) => ( - - ) ) } -
-
-
- ), -}; - export const Loading: Story = { ...Default, args: { From 77aa5d99241b5b3511a3d20af7279855bd46f85f Mon Sep 17 00:00:00 2001 From: Lena Morita Date: Sat, 30 May 2026 04:27:29 +0900 Subject: [PATCH 7/8] Clarify button icon margin --- packages/ui/src/button/style.module.css | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/packages/ui/src/button/style.module.css b/packages/ui/src/button/style.module.css index 0e4230a98e4ac7..a4c1e510c2c74c 100644 --- a/packages/ui/src/button/style.module.css +++ b/packages/ui/src/button/style.module.css @@ -25,7 +25,7 @@ --wp-ui-button-min-width: calc(4ch + 2 * var(--wp-ui-button-padding-inline)); /* Icons are rendered as 24px but need to occupy a 16px footprint */ /* TODO: replace hardcoded values with DS size tokens once available */ - --wp-ui-button-icon-margin: calc((24px - 16px) / 2); + --wp-ui-button-icon-margin: calc((16px - 24px) / 2); /* by default, borders have the same color as the background */ --wp-ui-button-border-color: var(--wp-ui-button-background-color); @@ -146,7 +146,7 @@ } .icon { - margin: calc(-1 * var(--wp-ui-button-icon-margin)); + margin: var(--wp-ui-button-icon-margin); } /* Variants (`tone` + `variant`) overrides compared to default styles */ From 755e0a155a44923ba081c075aceace20999d2754 Mon Sep 17 00:00:00 2001 From: Lena Morita Date: Sat, 30 May 2026 04:32:18 +0900 Subject: [PATCH 8/8] Move changelog --- packages/ui/CHANGELOG.md | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/packages/ui/CHANGELOG.md b/packages/ui/CHANGELOG.md index e39438df0e4216..73c69b8e4e78ef 100644 --- a/packages/ui/CHANGELOG.md +++ b/packages/ui/CHANGELOG.md @@ -2,6 +2,10 @@ ## Unreleased +### Bug Fixes + +- `Button.Icon`: Preserve icon view boxes so icons with non-standard `viewBox` values are not clipped ([#78614](https://github.com/WordPress/gutenberg/pull/78614)). + ### Enhancements - `Tooltip.Provider`: Widen the types to accept all props of the equivalent `Tooltip.Provider` from `@base-ui/react` (types-only change) ([#78642](https://github.com/WordPress/gutenberg/pull/78642)). @@ -27,7 +31,6 @@ ### Bug Fixes -- `Button.Icon`: Preserve icon view boxes so icons with non-standard `viewBox` values are not clipped ([#78614](https://github.com/WordPress/gutenberg/pull/78614)). - `IconButton`: Default `focusableWhenDisabled` to `true`, matching `Button` ([#78526](https://github.com/WordPress/gutenberg/pull/78526)). - `Button`: Do not show the interactive cursor when disabled ([#78479](https://github.com/WordPress/gutenberg/pull/78479)). - `Button`: Fix disabled and hover styles for neutral minimal buttons with `aria-pressed="true"` ([#78635](https://github.com/WordPress/gutenberg/pull/78635)).