Skip to content

Commit e60fb42

Browse files
authored
Centralize S2 icon wrapper (#7113)
1 parent 71072b8 commit e60fb42

File tree

3 files changed

+50
-55
lines changed

3 files changed

+50
-55
lines changed

packages/@react-spectrum/s2/src/Icon.tsx

Lines changed: 45 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -11,10 +11,12 @@
1111
*/
1212

1313
import {AriaLabelingProps, DOMProps} from '@react-types/shared';
14+
import {ComponentType, Context, createContext, FunctionComponent, ReactNode, SVGProps, useRef} from 'react';
1415
import {ContextValue, SlotProps} from 'react-aria-components';
15-
import {createContext, ReactNode} from 'react';
16+
import {SkeletonWrapper, useSkeletonIcon} from './Skeleton';
1617
import {StyleString} from '../style/types';
1718
import {UnsafeStyles} from './style-utils' with {type: 'macro'};
19+
import {useSpectrumContextProps} from './useSpectrumContextProps';
1820

1921
export interface IconProps extends UnsafeStyles, SlotProps, AriaLabelingProps, DOMProps {
2022
'aria-hidden'?: boolean | 'false' | 'true'
@@ -31,3 +33,45 @@ export interface IllustrationContextValue extends IconContextValue {
3133

3234
export const IconContext = createContext<ContextValue<IconContextValue, SVGElement>>({});
3335
export const IllustrationContext = createContext<ContextValue<IllustrationContextValue, SVGElement>>({});
36+
37+
export function createIcon(Component: ComponentType<SVGProps<SVGSVGElement>>, context: Context<ContextValue<IconContextValue, SVGElement>> = IconContext): FunctionComponent<IconProps> {
38+
return (props: IconProps) => {
39+
let ref = useRef<SVGElement>(null);
40+
let ctx;
41+
// TODO: remove this default once we release RAC and use DEFAULT_SLOT.
42+
[ctx, ref] = useSpectrumContextProps({slot: props.slot || 'icon'} as IconContextValue, ref, context);
43+
let {render, styles} = ctx;
44+
let {
45+
UNSAFE_className,
46+
UNSAFE_style,
47+
slot,
48+
'aria-label': ariaLabel,
49+
'aria-hidden': ariaHidden,
50+
...otherProps
51+
} = props;
52+
53+
if (!ariaHidden) {
54+
ariaHidden = undefined;
55+
}
56+
57+
let svg = (
58+
<SkeletonWrapper>
59+
<Component
60+
{...otherProps}
61+
focusable={false}
62+
aria-label={ariaLabel}
63+
aria-hidden={ariaLabel ? (ariaHidden || undefined) : true}
64+
role="img"
65+
data-slot={slot}
66+
className={(UNSAFE_className ?? '') + ' ' + useSkeletonIcon(styles)}
67+
style={UNSAFE_style} />
68+
</SkeletonWrapper>
69+
);
70+
71+
if (render) {
72+
return render(svg);
73+
}
74+
75+
return svg;
76+
};
77+
}

packages/@react-spectrum/s2/src/index.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -40,7 +40,7 @@ export {DialogContainer, useDialogContainer} from './DialogContainer';
4040
export {Divider, DividerContext} from './Divider';
4141
export {DropZone, DropZoneContext} from './DropZone';
4242
export {Form} from './Form';
43-
export {IconContext, IllustrationContext} from './Icon';
43+
export {createIcon, IconContext, IllustrationContext} from './Icon';
4444
export {IllustratedMessage, IllustratedMessageContext} from './IllustratedMessage';
4545
export {Image, ImageContext} from './Image';
4646
export {ImageCoordinator} from './ImageCoordinator';

packages/dev/parcel-transformer-s2-icon/IconTransformer.js

Lines changed: 4 additions & 53 deletions
Original file line numberDiff line numberDiff line change
@@ -61,7 +61,7 @@ module.exports = new Transformer({
6161
plugins: ['@svgr/plugin-svgo', '@svgr/plugin-jsx']
6262
})
6363
).replace('export default ForwardRef;', '');
64-
let newFile = template(asset, iconName, optimized);
64+
let newFile = template(asset, optimized);
6565
return [{
6666
type: 'tsx',
6767
content: newFile,
@@ -72,65 +72,16 @@ module.exports = new Transformer({
7272
}
7373
});
7474

75-
function template(asset, iconName, svg) {
76-
let importName = iconName
77-
.replace(/^S2_Icon_(.*?)_\d+(?:x\d+)?_N$/, '$1')
78-
.replace(/^S2_(fill|lin)_(.+)_(.+_)?(\d+)$/, (m, name) => name[0].toUpperCase() + name.slice(1));
79-
let iconRename = importName;
80-
if (/^[0-9]/.test(importName)) {
81-
iconRename = '_' + importName;
82-
}
75+
function template(asset, svg) {
8376
let normalizedPath = asset.filePath.replaceAll('\\', '/');
8477
let context = asset.pipeline === 'illustration' || normalizedPath.includes('@react-spectrum/s2/spectrum-illustrations') ? 'IllustrationContext' : 'IconContext';
8578
return (
8679
`
87-
import {IconProps, ${context}, IconContextValue} from '${normalizedPath.includes('@react-spectrum/s2') ? '~/src/Icon' : '@react-spectrum/s2'}';
88-
import {SVGProps, useRef} from 'react';
89-
import {useContextProps} from 'react-aria-components';
90-
import {SkeletonWrapper, useSkeletonIcon} from '~/src/Skeleton';
80+
import {createIcon, ${context}} from '${normalizedPath.includes('@react-spectrum/s2') ? '~/src/Icon' : '@react-spectrum/s2'}';
9181
9282
${svg.replace('import { SVGProps } from "react";', '')}
9383
94-
export default function ${iconRename}(props: IconProps) {
95-
let ref = useRef<SVGElement>(null);
96-
let ctx;
97-
// TODO: remove this default once we release RAC and use DEFAULT_SLOT.
98-
[ctx, ref] = useContextProps({slot: props.slot || 'icon'} as IconContextValue, ref, ${context});
99-
let {render, styles} = ctx;
100-
let {
101-
UNSAFE_className,
102-
UNSAFE_style,
103-
slot,
104-
'aria-label': ariaLabel,
105-
'aria-hidden': ariaHidden,
106-
...otherProps
107-
} = props;
108-
109-
if (!ariaHidden) {
110-
ariaHidden = undefined;
111-
}
112-
113-
let svg = (
114-
<SkeletonWrapper>
115-
<ForwardRef
116-
{...otherProps}
117-
focusable={false}
118-
aria-label={ariaLabel}
119-
aria-hidden={ariaLabel ? (ariaHidden || undefined) : true}
120-
role="img"
121-
data-slot={slot}
122-
className={(UNSAFE_className ?? '') + ' ' + useSkeletonIcon(styles)}
123-
style={UNSAFE_style} />
124-
</SkeletonWrapper>
125-
);
126-
127-
if (render) {
128-
return render(svg);
129-
}
130-
131-
return svg;
132-
}
133-
84+
export default /*#__PURE__*/ createIcon(ForwardRef, ${context});
13485
`
13586
);
13687
}

0 commit comments

Comments
 (0)