Skip to content

Commit ea092de

Browse files
jasmussenclaude
andcommitted
Icons: Add stroke-width support
Adds a strokeWidth prop to the React Icon component and expands the PHP icon registry's wp_kses allowlist to permit stroke-related attributes (stroke, stroke-width, stroke-linecap, stroke-linejoin, vector-effect, fill). Both changes are backwards compatible: the prop is a no-op for fill-based icons, and existing icons render unchanged. Updates square.svg to the new convention as a canary: moves stroke attributes to the outer <svg> so the prop can override them, and adds vector-effect="non-scaling-stroke" so the stroke renders at a constant pixel weight at any rendered size. Adds a Stroke width RangeControl to the icons library Storybook story. Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
1 parent ba624a2 commit ea092de

4 files changed

Lines changed: 62 additions & 20 deletions

File tree

lib/compat/wordpress-7.0/class-wp-icons-registry.php

Lines changed: 32 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -180,27 +180,42 @@ protected function register( $icon_name, $icon_properties ) {
180180
protected function sanitize_icon_content( $icon_content ) {
181181
$allowed_tags = array(
182182
'svg' => array(
183-
'class' => true,
184-
'xmlns' => true,
185-
'width' => true,
186-
'height' => true,
187-
'viewbox' => true,
188-
'aria-hidden' => true,
189-
'role' => true,
190-
'focusable' => true,
183+
'class' => true,
184+
'xmlns' => true,
185+
'width' => true,
186+
'height' => true,
187+
'viewbox' => true,
188+
'aria-hidden' => true,
189+
'role' => true,
190+
'focusable' => true,
191+
'fill' => true,
192+
'stroke' => true,
193+
'stroke-width' => true,
194+
'stroke-linecap' => true,
195+
'stroke-linejoin' => true,
191196
),
192197
'path' => array(
193-
'fill' => true,
194-
'fill-rule' => true,
195-
'd' => true,
196-
'transform' => true,
198+
'fill' => true,
199+
'fill-rule' => true,
200+
'd' => true,
201+
'transform' => true,
202+
'stroke' => true,
203+
'stroke-width' => true,
204+
'stroke-linecap' => true,
205+
'stroke-linejoin' => true,
206+
'vector-effect' => true,
197207
),
198208
'polygon' => array(
199-
'fill' => true,
200-
'fill-rule' => true,
201-
'points' => true,
202-
'transform' => true,
203-
'focusable' => true,
209+
'fill' => true,
210+
'fill-rule' => true,
211+
'points' => true,
212+
'transform' => true,
213+
'focusable' => true,
214+
'stroke' => true,
215+
'stroke-width' => true,
216+
'stroke-linecap' => true,
217+
'stroke-linejoin' => true,
218+
'vector-effect' => true,
204219
),
205220
);
206221
return wp_kses( $icon_content, $allowed_tags );

packages/icons/src/icon/index.ts

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,13 @@ export interface IconProps extends SVGProps {
2020
* @default 24
2121
*/
2222
size?: number;
23+
/**
24+
* Width of strokes in pixels. Applied to the outer `<svg>` as
25+
* `stroke-width`, inherited by stroked paths. Has no visible effect on
26+
* fill-based icons. Source SVGs of stroke-based icons carry their own
27+
* default; this prop overrides it per call.
28+
*/
29+
strokeWidth?: number;
2330
}
2431

2532
/**
@@ -30,12 +37,13 @@ export interface IconProps extends SVGProps {
3037
* @return Icon component
3138
*/
3239
export default forwardRef< HTMLElement, IconProps >(
33-
( { icon, size = 24, ...props }, ref ) => {
40+
( { icon, size = 24, strokeWidth, ...props }, ref ) => {
3441
return cloneElement(
3542
icon as ReactElement< React.RefAttributes< Element > >,
3643
{
3744
width: size,
3845
height: size,
46+
...( strokeWidth !== undefined && { strokeWidth } ),
3947
...props,
4048
ref,
4149
}
Lines changed: 2 additions & 2 deletions
Loading

storybook/stories/icons/library.story.tsx

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@ import {
1616
__experimentalToggleGroupControl as ToggleGroupControl,
1717
__experimentalToggleGroupControlOption as ToggleGroupControlOption,
1818
ToggleControl,
19+
RangeControl,
1920
} from '@wordpress/components';
2021

2122
/**
@@ -79,6 +80,7 @@ const meta: Meta = {
7980
argTypes: {
8081
filter: { control: false },
8182
size: { control: false },
83+
strokeWidth: { control: false },
8284
highlightPublicIcons: { control: false },
8385
},
8486
};
@@ -87,6 +89,7 @@ export default meta;
8789
type LibraryArgs = {
8890
filter: string;
8991
size: string | number;
92+
strokeWidth: number;
9093
highlightPublicIcons: boolean;
9194
};
9295

@@ -97,6 +100,7 @@ type LibraryExampleProps = LibraryArgs & {
97100
const LibraryExample = ( {
98101
filter,
99102
size,
103+
strokeWidth,
100104
highlightPublicIcons,
101105
updateArgs,
102106
}: LibraryExampleProps ): ReactElement => {
@@ -148,6 +152,19 @@ const LibraryExample = ( {
148152
/>
149153
) ) }
150154
</ToggleGroupControl>
155+
<div style={ { width: 200 } }>
156+
<RangeControl
157+
__next40pxDefaultSize
158+
label="Stroke width"
159+
value={ strokeWidth }
160+
onChange={ ( value: number | undefined ) =>
161+
updateArgs( { strokeWidth: value } )
162+
}
163+
min={ 0.5 }
164+
max={ 5 }
165+
step={ 0.25 }
166+
/>
167+
</div>
151168
<ToggleControl
152169
label="Highlight public icons"
153170
checked={ highlightPublicIcons }
@@ -186,6 +203,7 @@ const LibraryExample = ( {
186203
<Icon
187204
icon={ icon }
188205
size={ Number( size ) }
206+
strokeWidth={ strokeWidth }
189207
/>
190208
<span
191209
style={ {
@@ -213,6 +231,7 @@ export const Library: StoryObj< typeof meta > = {
213231
args: {
214232
filter: '',
215233
size: '24',
234+
strokeWidth: 1.5,
216235
highlightPublicIcons: false,
217236
},
218237
render: function Library() {

0 commit comments

Comments
 (0)