diff --git a/CHANGELOG.md b/CHANGELOG.md
index cf638d4d155..5d70cffa87b 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -1,6 +1,8 @@
## [`master`](https://github.com/elastic/eui/tree/master)
-No public interface changes since `38.1.0`.
+**Bug fixes**
+
+- Fixed logo icons with static SVG IDs causing accessibility errors when multiples of the same logo were present ([#5204](https://github.com/elastic/eui/pull/5204))
## [`38.1.0`](https://github.com/elastic/eui/tree/v38.1.0)
diff --git a/scripts/compile-icons.js b/scripts/compile-icons.js
index a42ef619eb9..f1f84046439 100644
--- a/scripts/compile-icons.js
+++ b/scripts/compile-icons.js
@@ -16,21 +16,24 @@ function pascalCase(x) {
const iconFiles = glob.sync('**/*.svg', { cwd: iconsDir, realpath: true });
iconFiles.forEach(async (filePath) => {
+ const fileName = path.basename(filePath, '.svg');
const svgSource = fs.readFileSync(filePath);
+ const svgString = svgSource.toString();
try {
- const viewBoxPosition = svgSource.toString().indexOf('viewBox');
- if (viewBoxPosition === -1) {
+ if (!svgString.includes('viewBox')) {
throw new Error(`${filePath} is missing a 'viewBox' attribute`);
}
- const jsxSource = await svgr(
+ const hasIds = svgString.includes('id="');
+
+ let jsxSource = await svgr(
svgSource,
{
plugins: ['@svgr/plugin-svgo', '@svgr/plugin-jsx'],
svgoConfig: {
plugins: [
- { cleanupIDs: false },
+ { cleanupIDs: true },
{ prefixIds: false },
{ removeViewBox: false },
],
@@ -43,17 +46,38 @@ iconFiles.forEach(async (filePath) => {
{ template },
opts,
{ imports, componentName, props, jsx }
- ) => template.ast`
+ ) =>
+ hasIds
+ ? template.ast`
+${imports}
+import { htmlIdGenerator } from '../../../services';
+const ${componentName} = (${props}) => {
+ const generateId = htmlIdGenerator('${fileName}');
+ return (
+ ${jsx}
+ );
+};
+export const icon = ${componentName};
+`
+ : template.ast`
${imports}
const ${componentName} = (${props}) => ${jsx}
export const icon = ${componentName};
- `,
+`,
},
{
- componentName: `EuiIcon${pascalCase(path.basename(filePath, '.svg'))}`,
+ componentName: `EuiIcon${pascalCase(fileName)}`,
}
);
+ // Replace static SVGs IDs with dynamic JSX that uses the htmlIdGenerator
+ if (hasIds) {
+ jsxSource = jsxSource
+ .replace(/id="(\S+)"/gi, "id={generateId('$1')}")
+ .replace(/"url\(#(\S+)\)"/gi, "{`url(#${generateId('$1')})`}")
+ .replace(/xlinkHref="#(\S+)"/gi, "xlinkHref={`#${generateId('$1')}`}");
+ }
+
const outputFilePath = filePath.replace(/\.svg$/, '.js');
const comment = '// THIS IS A GENERATED FILE. DO NOT MODIFY MANUALLY\n\n';
fs.writeFileSync(outputFilePath, comment + jsxSource);
diff --git a/src/components/icon/__snapshots__/icon.test.tsx.snap b/src/components/icon/__snapshots__/icon.test.tsx.snap
index da324d45aee..4533372f50d 100644
--- a/src/components/icon/__snapshots__/icon.test.tsx.snap
+++ b/src/components/icon/__snapshots__/icon.test.tsx.snap
@@ -3761,7 +3761,7 @@ exports[`EuiIcon props type logoApache is rendered 1`] = `
>
@@ -4255,11 +4255,11 @@ exports[`EuiIcon props type logoDropwizard is rendered 1`] = `
>
@@ -4911,7 +4911,7 @@ exports[`EuiIcon props type logoHAproxy is rendered 1`] = `
xmlns="http://www.w3.org/2000/svg"
>
@@ -5321,7 +5321,7 @@ exports[`EuiIcon props type logoMemcached is rendered 1`] = `
cy="42.708%"
fx="41.406%"
fy="42.708%"
- id="logo_memcached-c"
+ id="logo_memcached_generated-id_c"
r="0%"
>
`;
diff --git a/src/components/icon/assets/logo_apache.js b/src/components/icon/assets/logo_apache.js
index 746c8c74ce5..dc155aa00b3 100644
--- a/src/components/icon/assets/logo_apache.js
+++ b/src/components/icon/assets/logo_apache.js
@@ -1,140 +1,144 @@
// THIS IS A GENERATED FILE. DO NOT MODIFY MANUALLY
import * as React from 'react';
+import { htmlIdGenerator } from '../../../services';
-const EuiIconLogoApache = ({ title, titleId, ...props }) => (
-
-);
+const EuiIconLogoApache = ({ title, titleId, ...props }) => {
+ const generateId = htmlIdGenerator('logo_apache');
+ return (
+
+ );
+};
export const icon = EuiIconLogoApache;
diff --git a/src/components/icon/assets/logo_dropwizard.js b/src/components/icon/assets/logo_dropwizard.js
index 472e28bf7fe..7c9bbbcfad3 100644
--- a/src/components/icon/assets/logo_dropwizard.js
+++ b/src/components/icon/assets/logo_dropwizard.js
@@ -1,67 +1,71 @@
// THIS IS A GENERATED FILE. DO NOT MODIFY MANUALLY
import * as React from 'react';
+import { htmlIdGenerator } from '../../../services';
-const EuiIconLogoDropwizard = ({ title, titleId, ...props }) => (
-
-);
+const EuiIconLogoDropwizard = ({ title, titleId, ...props }) => {
+ const generateId = htmlIdGenerator('logo_dropwizard');
+ return (
+
+ );
+};
export const icon = EuiIconLogoDropwizard;
diff --git a/src/components/icon/assets/logo_dropwizard.svg b/src/components/icon/assets/logo_dropwizard.svg
index c62372fa8e8..5bd368ec9bf 100644
--- a/src/components/icon/assets/logo_dropwizard.svg
+++ b/src/components/icon/assets/logo_dropwizard.svg
@@ -1,17 +1,17 @@
+ );
+};
export const icon = EuiIconLogoGcp;
diff --git a/src/components/icon/assets/logo_google_g.js b/src/components/icon/assets/logo_google_g.js
index 17cb77e1909..495c7dcda54 100644
--- a/src/components/icon/assets/logo_google_g.js
+++ b/src/components/icon/assets/logo_google_g.js
@@ -1,82 +1,86 @@
// THIS IS A GENERATED FILE. DO NOT MODIFY MANUALLY
import * as React from 'react';
+import { htmlIdGenerator } from '../../../services';
-const EuiIconLogoGoogleG = ({ title, titleId, ...props }) => (
-
- {title ? {title} : null}
-
-
-
-
-
-
-
-
-
-
-
+const EuiIconLogoGoogleG = ({ title, titleId, ...props }) => {
+ const generateId = htmlIdGenerator('logo_google_g');
+ return (
+
+ {title ? {title} : null}
+
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
-
-
-);
+
+ );
+};
export const icon = EuiIconLogoGoogleG;
diff --git a/src/components/icon/assets/logo_haproxy.js b/src/components/icon/assets/logo_haproxy.js
index d960a45fa92..36370a837dc 100644
--- a/src/components/icon/assets/logo_haproxy.js
+++ b/src/components/icon/assets/logo_haproxy.js
@@ -1,84 +1,88 @@
// THIS IS A GENERATED FILE. DO NOT MODIFY MANUALLY
import * as React from 'react';
+import { htmlIdGenerator } from '../../../services';
-const EuiIconLogoHaproxy = ({ title, titleId, ...props }) => (
-
- {title ? {title} : null}
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-);
+const EuiIconLogoHaproxy = ({ title, titleId, ...props }) => {
+ const generateId = htmlIdGenerator('logo_haproxy');
+ return (
+
+ {title ? {title} : null}
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ );
+};
export const icon = EuiIconLogoHaproxy;
diff --git a/src/components/icon/assets/logo_ibm.js b/src/components/icon/assets/logo_ibm.js
index 6923b5c8f8f..634d865974c 100644
--- a/src/components/icon/assets/logo_ibm.js
+++ b/src/components/icon/assets/logo_ibm.js
@@ -1,106 +1,110 @@
// THIS IS A GENERATED FILE. DO NOT MODIFY MANUALLY
import * as React from 'react';
+import { htmlIdGenerator } from '../../../services';
-const EuiIconLogoIbm = ({ title, titleId, ...props }) => (
-
- {title ? {title} : null}
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-);
+const EuiIconLogoIbm = ({ title, titleId, ...props }) => {
+ const generateId = htmlIdGenerator('logo_ibm');
+ return (
+
+ {title ? {title} : null}
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ );
+};
export const icon = EuiIconLogoIbm;
diff --git a/src/components/icon/assets/logo_memcached.js b/src/components/icon/assets/logo_memcached.js
index 5c3f5138d75..3d7fd7aef4b 100644
--- a/src/components/icon/assets/logo_memcached.js
+++ b/src/components/icon/assets/logo_memcached.js
@@ -1,84 +1,94 @@
// THIS IS A GENERATED FILE. DO NOT MODIFY MANUALLY
import * as React from 'react';
+import { htmlIdGenerator } from '../../../services';
-const EuiIconLogoMemcached = ({ title, titleId, ...props }) => (
-
- {title ? {title} : null}
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-);
+const EuiIconLogoMemcached = ({ title, titleId, ...props }) => {
+ const generateId = htmlIdGenerator('logo_memcached');
+ return (
+
+ {title ? {title} : null}
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ );
+};
export const icon = EuiIconLogoMemcached;
diff --git a/src/components/icon/assets/logo_php.js b/src/components/icon/assets/logo_php.js
index b2ed53c6b2a..9b52bec56ea 100644
--- a/src/components/icon/assets/logo_php.js
+++ b/src/components/icon/assets/logo_php.js
@@ -1,89 +1,101 @@
// THIS IS A GENERATED FILE. DO NOT MODIFY MANUALLY
import * as React from 'react';
+import { htmlIdGenerator } from '../../../services';
-const EuiIconLogoPhp = ({ title, titleId, ...props }) => (
-
- {title ? {title} : null}
-
-
-
-
- {
+ const generateId = htmlIdGenerator('logo_php');
+ return (
+
+ {title ? {title} : null}
+
+
+
+
+
+
+
+
+
+
+
+
-
-
-
-
-
-
-
-
-
-
+
+
+
+
+
+
+
+
+
+
-
-
+
+
+
+
+
+
+
+
+
+
+
+
+
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-);
+
+ );
+};
export const icon = EuiIconLogoPhp;
diff --git a/src/components/icon/assets/stop_slash.js b/src/components/icon/assets/stop_slash.js
index 13d8e89d65e..4352ef68c57 100644
--- a/src/components/icon/assets/stop_slash.js
+++ b/src/components/icon/assets/stop_slash.js
@@ -12,10 +12,7 @@ const EuiIconStopSlash = ({ title, titleId, ...props }) => (
{...props}
>
{title ? {title} : null}
-
+
);
diff --git a/src/components/icon/assets/stop_slash.svg b/src/components/icon/assets/stop_slash.svg
index 31d24729f97..7ef4b0cf4c7 100644
--- a/src/components/icon/assets/stop_slash.svg
+++ b/src/components/icon/assets/stop_slash.svg
@@ -1,3 +1,3 @@
-
+