Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat(Icon): forward ref #929

Merged
merged 2 commits into from
Aug 22, 2023
Merged
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
180 changes: 89 additions & 91 deletions src/components/Icon/Icon.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,10 @@ import './Icon.scss';

export type IconData = SVGIconData;

interface IconComposition {
prefix?: string;
}

export interface IconProps extends QAProps {
data: IconData;
width?: number | string;
Expand All @@ -30,110 +34,104 @@ export interface IconProps extends QAProps {

const b = block('icon');

export function Icon({
data,
width,
height,
size,
className,
fill = 'currentColor',
stroke = 'none',
qa,
}: IconProps) {
// This component supports four different ways to load and use icons:
// - svg-react-loader
// - svg-sprite-loader
// - @svgr/webpack
// - string with raw svg

let w, h;

if (size) {
w = size;
h = size;
}

if (width) {
w = width;
}

if (height) {
h = height;
}

// Parsing viewBox to get width and height in case they were not specified
// For svg-react-loader svg attributes are available in component defaultProps
// In case with @svgr/webpack svg attributes can be fetched from the react element
// after calling svgr-component without any propses
let viewBox: string | undefined;

if (isSpriteData(data)) {
({viewBox} = data);
} else if (isStringSvgData(data)) {
viewBox = getStringViewBox(data);
} else if (isComponentSvgData(data)) {
({viewBox} = data.defaultProps);
} else if (isSvgrData(data)) {
const el = data({});

if (el) {
({viewBox} = el.props);
export const Icon: React.ForwardRefExoticComponent<IconProps & React.RefAttributes<SVGSVGElement>> &
IconComposition = React.forwardRef<SVGSVGElement, IconProps>(
({data, width, height, size, className, fill = 'currentColor', stroke = 'none', qa}, ref) => {
// This component supports four different ways to load and use icons:
// - svg-react-loader
// - svg-sprite-loader
// - @svgr/webpack
// - string with raw svg

let w, h;

if (size) {
w = size;
h = size;
}
}

if (viewBox && (!w || !h)) {
const values = viewBox.split(/\s+|\s*,\s*/);
if (width) {
w = width;
}

if (!w) {
w = values[2];
if (height) {
h = height;
}
if (!h) {
h = values[3];

// Parsing viewBox to get width and height in case they were not specified
// For svg-react-loader svg attributes are available in component defaultProps
// In case with @svgr/webpack svg attributes can be fetched from the react element
// after calling svgr-component without any propses
let viewBox: string | undefined;

if (isSpriteData(data)) {
({viewBox} = data);
} else if (isStringSvgData(data)) {
viewBox = getStringViewBox(data);
} else if (isComponentSvgData(data)) {
({viewBox} = data.defaultProps);
} else if (isSvgrData(data)) {
const el = data({});

if (el) {
({viewBox} = el.props);
}
}
}

const props = {
xmlns: 'http://www.w3.org/2000/svg',
xmlnsXlink: 'http://www.w3.org/1999/xlink',
width: w,
height: h,
className: b(null, className),
fill,
stroke,
'data-qa': qa,
...a11yHiddenSvgProps,
};
if (viewBox && (!w || !h)) {
const values = viewBox.split(/\s+|\s*,\s*/);

if (isStringSvgData(data)) {
const preparedData = prepareStringData(data);
if (!w) {
w = values[2];
}
if (!h) {
h = values[3];
}
}

return <svg {...props} dangerouslySetInnerHTML={{__html: preparedData}} />;
}
const props = {
xmlns: 'http://www.w3.org/2000/svg',
xmlnsXlink: 'http://www.w3.org/1999/xlink',
width: w,
height: h,
className: b(null, className),
fill,
stroke,
'data-qa': qa,
...a11yHiddenSvgProps,
};

if (isStringSvgData(data)) {
const preparedData = prepareStringData(data);

return <svg {...props} ref={ref} dangerouslySetInnerHTML={{__html: preparedData}} />;
}

if (isSpriteData(data)) {
const href = Icon.prefix + (data.url || `#${data.id}`);
if (isSpriteData(data)) {
const href = Icon.prefix + (data.url || `#${data.id}`);

return (
<svg {...props} viewBox={viewBox}>
<use href={href} xlinkHref={href} />
</svg>
);
}
return (
<svg {...props} viewBox={viewBox} ref={ref}>
<use href={href} xlinkHref={href} />
</svg>
);
}

// SVG wrapping is needed for compability with sprite-loader
// So we removing width and height for internal component so only external one is specifying them
// SVG wrapping is needed for compability with sprite-loader
// So we removing width and height for internal component so only external one is specifying them

const IconComponent = data;
if (IconComponent.defaultProps) {
IconComponent.defaultProps.width = IconComponent.defaultProps.height = undefined;
}
const IconComponent = data;
if (IconComponent.defaultProps) {
IconComponent.defaultProps.width = IconComponent.defaultProps.height = undefined;
}

return (
<svg {...props}>
<IconComponent width={undefined} height={undefined} />
</svg>
);
}
return (
<svg {...props} ref={ref}>
<IconComponent width={undefined} height={undefined} />
</svg>
);
},
);

Icon.displayName = 'Icon';
Icon.prefix = '';
Loading