Skip to content

Commit c309912

Browse files
authored
Wrap Icon component's SVG in a div to fix Safari rendering issue (#931)
* wrap icon in a wrapper element * render icon wrapper as div * update snapshots * add changeset * fix failing tests * remove destructuring of imports
1 parent 010578b commit c309912

File tree

6 files changed

+70
-18
lines changed

6 files changed

+70
-18
lines changed

.changeset/grumpy-countries-fail.md

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
---
2+
'@primer/react-brand': patch
3+
---
4+
5+
Fixed a bug in Safari where an `Icon` component with `hasBackground={true}` would cut off the corners of the rendered SVG. To resolve this the `Icon` component now wraps the rendered SVG in a div.
27 Bytes
Loading

packages/react/src/Icon/Icon.module.css

Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,41 @@
33
padding: 0;
44
}
55

6+
.Icon--size-20 {
7+
width: var(--base-size-20);
8+
height: var(--base-size-20);
9+
}
10+
11+
.Icon--size-24 {
12+
width: var(--base-size-24);
13+
height: var(--base-size-24);
14+
}
15+
16+
.Icon--size-28 {
17+
width: var(--base-size-28);
18+
height: var(--base-size-28);
19+
}
20+
21+
.Icon--size-32 {
22+
width: var(--base-size-32);
23+
height: var(--base-size-32);
24+
}
25+
26+
.Icon--size-36 {
27+
width: var(--base-size-36);
28+
height: var(--base-size-36);
29+
}
30+
31+
.Icon--size-40 {
32+
width: var(--base-size-40);
33+
height: var(--base-size-40);
34+
}
35+
36+
.Icon--size-44 {
37+
width: var(--base-size-44);
38+
height: var(--base-size-44);
39+
}
40+
641
.Icon--background {
742
padding: var(--base-size-12);
843
border-radius: var(--brand-borderRadius-full);

packages/react/src/Icon/Icon.module.css.d.ts

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,13 @@ declare const styles: {
2929
readonly "Icon--color-red": string;
3030
readonly "Icon--color-teal": string;
3131
readonly "Icon--color-yellow": string;
32+
readonly "Icon--size-20": string;
33+
readonly "Icon--size-24": string;
34+
readonly "Icon--size-28": string;
35+
readonly "Icon--size-32": string;
36+
readonly "Icon--size-36": string;
37+
readonly "Icon--size-40": string;
38+
readonly "Icon--size-44": string;
3239
};
3340
export = styles;
3441

packages/react/src/Icon/Icon.test.tsx

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -22,14 +22,14 @@ describe('Icon', () => {
2222
expect(getByLabelText('Git merge icon')).toBeInTheDocument()
2323
})
2424

25-
it('forwards className to the icon', () => {
25+
it('forwards className to the wrapper element', () => {
2626
const {getByLabelText} = render(<Icon icon={GitMergeIcon} aria-label="Git merge icon" className="custom-class" />)
27-
expect(getByLabelText('Git merge icon')).toHaveClass('custom-class')
27+
expect(getByLabelText('Git merge icon').parentElement).toHaveClass('custom-class')
2828
})
2929

3030
it('sets the color of the icon', () => {
3131
const {getByLabelText} = render(<Icon icon={GitMergeIcon} aria-label="Git merge icon" color="blue" />)
32-
expect(getByLabelText('Git merge icon')).toHaveClass('Icon--color-blue')
32+
expect(getByLabelText('Git merge icon').parentElement).toHaveClass('Icon--color-blue')
3333
})
3434

3535
it('sets the size of the icon when `size` is a number', () => {

packages/react/src/Icon/Icon.tsx

Lines changed: 20 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
import React, {type SVGAttributes} from 'react'
1+
import React, {type ReactElement, type SVGAttributes} from 'react'
22
import styles from './Icon.module.css'
33
import clsx from 'clsx'
44
import {type Icon as OcticonProps} from '@primer/octicons-react'
@@ -37,7 +37,7 @@ export type IconColor = (typeof iconColors)[number]
3737
export const defaultIconColor = iconColors[0]
3838

3939
export type IconProps = SVGAttributes<SVGElement> & {
40-
icon: OcticonProps | React.ReactElement<OcticonProps>
40+
icon: OcticonProps | ReactElement<OcticonProps>
4141
color?: IconColor
4242
hasBackground?: boolean
4343
size?: IconSize
@@ -54,25 +54,30 @@ export const Icon = ({
5454
const iconSize = getIconSize(size)
5555

5656
const iconProps = {
57-
className: clsx(
58-
styles['Icon'],
59-
styles[`Icon--color-${color}`],
60-
hasBackground && [styles['Icon--background'], styles[`Icon--background-color-${color}`]],
61-
className,
62-
),
6357
size: iconSize,
6458
...rest,
6559
}
6660

67-
/**
68-
* Ensures that instantiated JSX can continue to be passed as props
69-
*/
70-
if (React.isValidElement(Octicon)) {
71-
return React.cloneElement(Octicon as React.ReactElement<OcticonProps>, {
61+
const iconComponent = React.isValidElement(Octicon) ? (
62+
React.cloneElement(Octicon as ReactElement<OcticonProps>, {
7263
...Octicon.props,
7364
...iconProps,
7465
})
75-
}
66+
) : (
67+
<Octicon {...iconProps} />
68+
)
7669

77-
return <Octicon {...iconProps} />
70+
return (
71+
<div
72+
className={clsx(
73+
styles['Icon'],
74+
styles[`Icon--size-${iconSize}`],
75+
styles[`Icon--color-${color}`],
76+
hasBackground && [styles['Icon--background'], styles[`Icon--background-color-${color}`]],
77+
className,
78+
)}
79+
>
80+
{iconComponent}
81+
</div>
82+
)
7883
}

0 commit comments

Comments
 (0)