Skip to content

Commit a0637d4

Browse files
authoredJun 19, 2022
feat: add descProp option (#729)
1 parent 846cd20 commit a0637d4

File tree

20 files changed

+534
-63
lines changed

20 files changed

+534
-63
lines changed
 

‎packages/babel-plugin-svg-dynamic-title/README.md

+4
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,10 @@ npm install --save-dev @svgr/babel-plugin-svg-dynamic-title
1616
}
1717
```
1818

19+
## Note
20+
21+
This plugin handles both the titleProp and descProp options. By default, it will handle titleProp only.
22+
1923
## License
2024

2125
MIT

‎packages/babel-plugin-svg-dynamic-title/src/index.test.ts

+69-8
Original file line numberDiff line numberDiff line change
@@ -1,16 +1,16 @@
11
import { transform } from '@babel/core'
2-
import plugin from '.'
2+
import plugin, { Options } from '.'
33

4-
const testPlugin = (code: string) => {
4+
const testPlugin = (code: string, options: Options = {tag: 'title'}) => {
55
const result = transform(code, {
6-
plugins: ['@babel/plugin-syntax-jsx', plugin],
6+
plugins: ['@babel/plugin-syntax-jsx', [plugin, options]],
77
configFile: false,
88
})
99

1010
return result?.code
1111
}
1212

13-
describe('plugin', () => {
13+
describe('title plugin', () => {
1414
it('should add title attribute if not present', () => {
1515
expect(testPlugin('<svg></svg>')).toMatchInlineSnapshot(
1616
`"<svg>{title ? <title id={titleId}>{title}</title> : null}</svg>;"`,
@@ -19,7 +19,9 @@ describe('plugin', () => {
1919

2020
it('should add title element and fallback to existing title', () => {
2121
// testing when the existing title contains a simple string
22-
expect(testPlugin(`<svg><title>Hello</title></svg>`)).toMatchInlineSnapshot(
22+
expect(
23+
testPlugin(`<svg><title>Hello</title></svg>`),
24+
).toMatchInlineSnapshot(
2325
`"<svg>{title === undefined ? <title id={titleId}>Hello</title> : title ? <title id={titleId}>{title}</title> : null}</svg>;"`,
2426
)
2527
// testing when the existing title contains an JSXExpression
@@ -38,19 +40,78 @@ describe('plugin', () => {
3840
)
3941
})
4042
it('should support empty title', () => {
41-
expect(testPlugin('<svg><title></title></svg>')).toMatchInlineSnapshot(
43+
expect(
44+
testPlugin('<svg><title></title></svg>'),
45+
).toMatchInlineSnapshot(
4246
`"<svg>{title ? <title id={titleId}>{title}</title> : null}</svg>;"`,
4347
)
4448
})
4549
it('should support self closing title', () => {
46-
expect(testPlugin('<svg><title /></svg>')).toMatchInlineSnapshot(
50+
expect(
51+
testPlugin('<svg><title /></svg>'),
52+
).toMatchInlineSnapshot(
4753
`"<svg>{title ? <title id={titleId}>{title}</title> : null}</svg>;"`,
4854
)
4955
})
5056

5157
it('should work if an attribute is already present', () => {
52-
expect(testPlugin('<svg><foo /></svg>')).toMatchInlineSnapshot(
58+
expect(
59+
testPlugin('<svg><foo /></svg>'),
60+
).toMatchInlineSnapshot(
5361
`"<svg>{title ? <title id={titleId}>{title}</title> : null}<foo /></svg>;"`,
5462
)
5563
})
5664
})
65+
66+
describe('desc plugin', () => {
67+
it('should add desc attribute if not present', () => {
68+
expect(testPlugin('<svg></svg>', { tag: 'desc' })).toMatchInlineSnapshot(
69+
`"<svg>{desc ? <desc id={descId}>{desc}</desc> : null}</svg>;"`,
70+
)
71+
})
72+
73+
it('should add desc element and fallback to existing desc', () => {
74+
// testing when the existing desc contains a simple string
75+
expect(
76+
testPlugin(`<svg><desc>Hello</desc></svg>`, { tag: 'desc' }),
77+
).toMatchInlineSnapshot(
78+
`"<svg>{desc === undefined ? <desc id={descId}>Hello</desc> : desc ? <desc id={descId}>{desc}</desc> : null}</svg>;"`,
79+
)
80+
// testing when the existing desc contains an JSXExpression
81+
expect(
82+
testPlugin(`<svg><desc>{"Hello"}</desc></svg>`, { tag: 'desc' }),
83+
).toMatchInlineSnapshot(
84+
`"<svg>{desc === undefined ? <desc id={descId}>{\\"Hello\\"}</desc> : desc ? <desc id={descId}>{desc}</desc> : null}</svg>;"`,
85+
)
86+
})
87+
it('should preserve any existing desc attributes', () => {
88+
// testing when the existing desc contains a simple string
89+
expect(
90+
testPlugin(`<svg><desc id='a'>Hello</desc></svg>`, { tag: 'desc' }),
91+
).toMatchInlineSnapshot(
92+
`"<svg>{desc === undefined ? <desc id={descId || 'a'}>Hello</desc> : desc ? <desc id={descId || 'a'}>{desc}</desc> : null}</svg>;"`,
93+
)
94+
})
95+
it('should support empty desc', () => {
96+
expect(
97+
testPlugin('<svg><desc></desc></svg>', { tag: 'desc' }),
98+
).toMatchInlineSnapshot(
99+
`"<svg>{desc ? <desc id={descId}>{desc}</desc> : null}</svg>;"`,
100+
)
101+
})
102+
it('should support self closing desc', () => {
103+
expect(
104+
testPlugin('<svg><desc /></svg>', { tag: 'desc' }),
105+
).toMatchInlineSnapshot(
106+
`"<svg>{desc ? <desc id={descId}>{desc}</desc> : null}</svg>;"`,
107+
)
108+
})
109+
110+
it('should work if an attribute is already present', () => {
111+
expect(
112+
testPlugin('<svg><foo /></svg>', { tag: 'desc' }),
113+
).toMatchInlineSnapshot(
114+
`"<svg>{desc ? <desc id={descId}>{desc}</desc> : null}<foo /></svg>;"`,
115+
)
116+
})
117+
})

‎packages/babel-plugin-svg-dynamic-title/src/index.ts

+41-26
Original file line numberDiff line numberDiff line change
@@ -3,45 +3,58 @@ import { NodePath, types as t } from '@babel/core'
33

44
const elements = ['svg', 'Svg']
55

6-
const createTitleElement = (
6+
type tag = 'title' | 'desc'
7+
8+
export interface Options {
9+
tag: tag | null
10+
}
11+
12+
interface State {
13+
opts: Options
14+
}
15+
16+
const createTagElement = (
17+
tag: tag,
718
children: t.JSXExpressionContainer[] = [],
819
attributes: (t.JSXAttribute | t.JSXSpreadAttribute)[] = [],
920
) => {
10-
const title = t.jsxIdentifier('title')
21+
const eleName = t.jsxIdentifier(tag)
1122
return t.jsxElement(
12-
t.jsxOpeningElement(title, attributes),
13-
t.jsxClosingElement(title),
23+
t.jsxOpeningElement(eleName, attributes),
24+
t.jsxClosingElement(eleName),
1425
children,
1526
)
1627
}
1728

18-
const createTitleIdAttribute = () =>
29+
const createTagIdAttribute = (tag: tag) =>
1930
t.jsxAttribute(
2031
t.jsxIdentifier('id'),
21-
t.jsxExpressionContainer(t.identifier('titleId')),
32+
t.jsxExpressionContainer(t.identifier(`${tag}Id`)),
2233
)
2334

24-
const addTitleIdAttribute = (
35+
const addTagIdAttribute = (
36+
tag: tag,
2537
attributes: (t.JSXAttribute | t.JSXSpreadAttribute)[],
2638
) => {
2739
const existingId = attributes.find(
2840
(attribute) => t.isJSXAttribute(attribute) && attribute.name.name === 'id',
2941
) as t.JSXAttribute | undefined
3042

3143
if (!existingId) {
32-
return [...attributes, createTitleIdAttribute()]
44+
return [...attributes, createTagIdAttribute(tag)]
3345
}
3446
existingId.value = t.jsxExpressionContainer(
3547
t.isStringLiteral(existingId.value)
36-
? t.logicalExpression('||', t.identifier('titleId'), existingId.value)
37-
: t.identifier('titleId'),
48+
? t.logicalExpression('||', t.identifier(`${tag}Id`), existingId.value)
49+
: t.identifier(`${tag}Id`),
3850
)
3951
return attributes
4052
}
4153

4254
const plugin = () => ({
4355
visitor: {
44-
JSXElement(path: NodePath<t.JSXElement>) {
56+
JSXElement(path: NodePath<t.JSXElement>, state: State) {
57+
const tag = state.opts.tag || 'title'
4558
if (!elements.length) return
4659

4760
const openingElement = path.get('openingElement')
@@ -54,22 +67,24 @@ const plugin = () => ({
5467
return
5568
}
5669

57-
const getTitleElement = (
70+
const getTagElement = (
5871
existingTitle?: t.JSXElement,
5972
): t.JSXExpressionContainer => {
60-
const titleExpression = t.identifier('title')
73+
const tagExpression = t.identifier(tag)
6174
if (existingTitle) {
62-
existingTitle.openingElement.attributes = addTitleIdAttribute(
75+
existingTitle.openingElement.attributes = addTagIdAttribute(
76+
tag,
6377
existingTitle.openingElement.attributes,
6478
)
6579
}
6680
const conditionalTitle = t.conditionalExpression(
67-
titleExpression,
68-
createTitleElement(
69-
[t.jsxExpressionContainer(titleExpression)],
81+
tagExpression,
82+
createTagElement(
83+
tag,
84+
[t.jsxExpressionContainer(tagExpression)],
7085
existingTitle
7186
? existingTitle.openingElement.attributes
72-
: [createTitleIdAttribute()],
87+
: [createTagIdAttribute(tag)],
7388
),
7489
t.nullLiteral(),
7590
)
@@ -80,7 +95,7 @@ const plugin = () => ({
8095
t.conditionalExpression(
8196
t.binaryExpression(
8297
'===',
83-
titleExpression,
98+
tagExpression,
8499
t.identifier('undefined'),
85100
),
86101
existingTitle,
@@ -92,25 +107,25 @@ const plugin = () => ({
92107
}
93108

94109
// store the title element
95-
let titleElement: t.JSXExpressionContainer | null = null
110+
let tagElement: t.JSXExpressionContainer | null = null
96111

97112
const hasTitle = path.get('children').some((childPath) => {
98-
if (childPath.node === titleElement) return false
113+
if (childPath.node === tagElement) return false
99114
if (!childPath.isJSXElement()) return false
100115
const name = childPath.get('openingElement').get('name')
101116
if (!name.isJSXIdentifier()) return false
102-
if (name.node.name !== 'title') return false
103-
titleElement = getTitleElement(childPath.node)
104-
childPath.replaceWith(titleElement)
117+
if (name.node.name !== tag) return false
118+
tagElement = getTagElement(childPath.node)
119+
childPath.replaceWith(tagElement)
105120
return true
106121
})
107122

108123
// create a title element if not already create
109-
titleElement = titleElement || getTitleElement()
124+
tagElement = tagElement || getTagElement()
110125
if (!hasTitle) {
111126
// path.unshiftContainer is not working well :(
112127
// path.unshiftContainer('children', titleElement)
113-
path.node.children.unshift(titleElement)
128+
path.node.children.unshift(tagElement)
114129
path.replaceWith(path.node)
115130
}
116131
},

‎packages/babel-plugin-transform-svg-component/src/__snapshots__/index.test.ts.snap

+126-4
Original file line numberDiff line numberDiff line change
@@ -97,6 +97,29 @@ const SvgComponent = () => <svg><g /></svg>;
9797
export default SvgComponent;"
9898
`;
9999
100+
exports[`plugin javascript with "descProp" adds "desc" and "descId" prop 1`] = `
101+
"import * as React from \\"react\\";
102+
103+
const SvgComponent = ({
104+
desc,
105+
descId
106+
}) => <svg><g /></svg>;
107+
108+
export default SvgComponent;"
109+
`;
110+
111+
exports[`plugin javascript with "descProp" and "expandProps" adds "desc", "descId" props and expands props 1`] = `
112+
"import * as React from \\"react\\";
113+
114+
const SvgComponent = ({
115+
desc,
116+
descId,
117+
...props
118+
}) => <svg><g /></svg>;
119+
120+
export default SvgComponent;"
121+
`;
122+
100123
exports[`plugin javascript with "expandProps" add props 1`] = `
101124
"import * as React from \\"react\\";
102125
@@ -194,7 +217,21 @@ const ForwardRef = forwardRef(SvgComponent);
194217
export default ForwardRef;"
195218
`;
196219
197-
exports[`plugin javascript with "titleProp" adds "titleProp" and "titleId" prop 1`] = `
220+
exports[`plugin javascript with "titleProp" "descProp" and "expandProps" adds "title", "titleId", "desc", "descId" props and expands props 1`] = `
221+
"import * as React from \\"react\\";
222+
223+
const SvgComponent = ({
224+
title,
225+
titleId,
226+
desc,
227+
descId,
228+
...props
229+
}) => <svg><g /></svg>;
230+
231+
export default SvgComponent;"
232+
`;
233+
234+
exports[`plugin javascript with "titleProp" adds "title" and "titleId" prop 1`] = `
198235
"import * as React from \\"react\\";
199236
200237
const SvgComponent = ({
@@ -205,7 +242,20 @@ const SvgComponent = ({
205242
export default SvgComponent;"
206243
`;
207244
208-
exports[`plugin javascript with "titleProp" and "expandProps" adds "titleProp", "titleId" props and expands props 1`] = `
245+
exports[`plugin javascript with "titleProp" and "descProp" adds "title", "titleId", "desc", and "descId prop 1`] = `
246+
"import * as React from \\"react\\";
247+
248+
const SvgComponent = ({
249+
title,
250+
titleId,
251+
desc,
252+
descId
253+
}) => <svg><g /></svg>;
254+
255+
export default SvgComponent;"
256+
`;
257+
258+
exports[`plugin javascript with "titleProp" and "expandProps" adds "title", "titleId" props and expands props 1`] = `
209259
"import * as React from \\"react\\";
210260
211261
const SvgComponent = ({
@@ -325,6 +375,38 @@ const SvgComponent = () => <svg><g /></svg>;
325375
export default SvgComponent;"
326376
`;
327377
378+
exports[`plugin typescript with "descProp" adds "desc" and "descId" prop 1`] = `
379+
"import * as React from \\"react\\";
380+
interface SVGRProps {
381+
desc?: string;
382+
descId?: string;
383+
}
384+
385+
const SvgComponent = ({
386+
desc,
387+
descId
388+
}: SVGRProps) => <svg><g /></svg>;
389+
390+
export default SvgComponent;"
391+
`;
392+
393+
exports[`plugin typescript with "descProp" and "expandProps" adds "desc", "descId" props and expands props 1`] = `
394+
"import * as React from \\"react\\";
395+
import { SVGProps } from \\"react\\";
396+
interface SVGRProps {
397+
desc?: string;
398+
descId?: string;
399+
}
400+
401+
const SvgComponent = ({
402+
desc,
403+
descId,
404+
...props
405+
}: SVGProps<SVGSVGElement> & SVGRProps) => <svg><g /></svg>;
406+
407+
export default SvgComponent;"
408+
`;
409+
328410
exports[`plugin typescript with "expandProps" add props 1`] = `
329411
"import * as React from \\"react\\";
330412
import { SVGProps } from \\"react\\";
@@ -423,7 +505,28 @@ const ForwardRef = forwardRef(SvgComponent);
423505
export default ForwardRef;"
424506
`;
425507
426-
exports[`plugin typescript with "titleProp" adds "titleProp" and "titleId" prop 1`] = `
508+
exports[`plugin typescript with "titleProp" "descProp" and "expandProps" adds "title", "titleId", "desc", "descId" props and expands props 1`] = `
509+
"import * as React from \\"react\\";
510+
import { SVGProps } from \\"react\\";
511+
interface SVGRProps {
512+
title?: string;
513+
titleId?: string;
514+
desc?: string;
515+
descId?: string;
516+
}
517+
518+
const SvgComponent = ({
519+
title,
520+
titleId,
521+
desc,
522+
descId,
523+
...props
524+
}: SVGProps<SVGSVGElement> & SVGRProps) => <svg><g /></svg>;
525+
526+
export default SvgComponent;"
527+
`;
528+
529+
exports[`plugin typescript with "titleProp" adds "title" and "titleId" prop 1`] = `
427530
"import * as React from \\"react\\";
428531
interface SVGRProps {
429532
title?: string;
@@ -438,7 +541,26 @@ const SvgComponent = ({
438541
export default SvgComponent;"
439542
`;
440543
441-
exports[`plugin typescript with "titleProp" and "expandProps" adds "titleProp", "titleId" props and expands props 1`] = `
544+
exports[`plugin typescript with "titleProp" and "descProp" adds "title", "titleId", "desc", and "descId prop 1`] = `
545+
"import * as React from \\"react\\";
546+
interface SVGRProps {
547+
title?: string;
548+
titleId?: string;
549+
desc?: string;
550+
descId?: string;
551+
}
552+
553+
const SvgComponent = ({
554+
title,
555+
titleId,
556+
desc,
557+
descId
558+
}: SVGRProps) => <svg><g /></svg>;
559+
560+
export default SvgComponent;"
561+
`;
562+
563+
exports[`plugin typescript with "titleProp" and "expandProps" adds "title", "titleId" props and expands props 1`] = `
442564
"import * as React from \\"react\\";
443565
import { SVGProps } from \\"react\\";
444566
interface SVGRProps {

‎packages/babel-plugin-transform-svg-component/src/index.test.ts

+44-2
Original file line numberDiff line numberDiff line change
@@ -57,7 +57,7 @@ describe('plugin', () => {
5757
})
5858

5959
describe('with "titleProp"', () => {
60-
it('adds "titleProp" and "titleId" prop', () => {
60+
it('adds "title" and "titleId" prop', () => {
6161
const { code } = testPlugin(language)('<svg><g /></svg>', {
6262
titleProp: true,
6363
})
@@ -66,7 +66,7 @@ describe('plugin', () => {
6666
})
6767

6868
describe('with "titleProp" and "expandProps"', () => {
69-
it('adds "titleProp", "titleId" props and expands props', () => {
69+
it('adds "title", "titleId" props and expands props', () => {
7070
const { code } = testPlugin(language)('<svg><g /></svg>', {
7171
...defaultOptions,
7272
expandProps: true,
@@ -76,6 +76,48 @@ describe('plugin', () => {
7676
})
7777
})
7878

79+
describe('with "descProp"', () => {
80+
it('adds "desc" and "descId" prop', () => {
81+
const { code } = testPlugin(language)('<svg><g /></svg>', {
82+
descProp: true,
83+
})
84+
expect(code).toMatchSnapshot()
85+
})
86+
})
87+
88+
describe('with "descProp" and "expandProps"', () => {
89+
it('adds "desc", "descId" props and expands props', () => {
90+
const { code } = testPlugin(language)('<svg><g /></svg>', {
91+
...defaultOptions,
92+
expandProps: true,
93+
descProp: true,
94+
})
95+
expect(code).toMatchSnapshot()
96+
})
97+
})
98+
99+
describe('with "titleProp" and "descProp"', () => {
100+
it('adds "title", "titleId", "desc", and "descId prop', () => {
101+
const { code } = testPlugin(language)('<svg><g /></svg>', {
102+
titleProp: true,
103+
descProp: true,
104+
})
105+
expect(code).toMatchSnapshot()
106+
})
107+
})
108+
109+
describe('with "titleProp" "descProp" and "expandProps"', () => {
110+
it('adds "title", "titleId", "desc", "descId" props and expands props', () => {
111+
const { code } = testPlugin(language)('<svg><g /></svg>', {
112+
...defaultOptions,
113+
expandProps: true,
114+
titleProp: true,
115+
descProp: true,
116+
})
117+
expect(code).toMatchSnapshot()
118+
})
119+
})
120+
79121
describe('with "expandProps"', () => {
80122
it('add props', () => {
81123
const { code } = testPlugin(language)('<svg><g /></svg>', {

‎packages/babel-plugin-transform-svg-component/src/types.ts

+1
Original file line numberDiff line numberDiff line change
@@ -35,6 +35,7 @@ export interface JSXRuntimeImport {
3535
export interface Options {
3636
typescript?: boolean
3737
titleProp?: boolean
38+
descProp?: boolean
3839
expandProps?: boolean | 'start' | 'end'
3940
ref?: boolean
4041
template?: Template

‎packages/babel-plugin-transform-svg-component/src/variables.ts

+40-23
Original file line numberDiff line numberDiff line change
@@ -122,38 +122,55 @@ export const getVariables = ({
122122
)
123123
}
124124

125-
if (opts.titleProp) {
126-
const prop = t.objectPattern([
127-
t.objectProperty(
128-
t.identifier('title'),
129-
t.identifier('title'),
125+
if (opts.titleProp || opts.descProp) {
126+
const properties = []
127+
const propertySignatures = []
128+
const createProperty = (attr: string) => {
129+
return t.objectProperty(
130+
t.identifier(attr),
131+
t.identifier(attr),
130132
false,
131133
true,
132-
),
133-
t.objectProperty(
134-
t.identifier('titleId'),
135-
t.identifier('titleId'),
136-
false,
137-
true,
138-
),
139-
])
134+
)
135+
}
136+
const createSignature = (attr: string) => {
137+
return tsOptionalPropertySignature(
138+
t.identifier(attr),
139+
t.tsTypeAnnotation(t.tsStringKeyword()),
140+
)
141+
}
142+
143+
if (opts.titleProp) {
144+
properties.push(createProperty('title'), createProperty('titleId'))
145+
146+
if (opts.typescript) {
147+
propertySignatures.push(
148+
createSignature('title'),
149+
createSignature('titleId'),
150+
)
151+
}
152+
}
153+
154+
if (opts.descProp) {
155+
properties.push(createProperty('desc'), createProperty('descId'))
156+
157+
if (opts.typescript) {
158+
propertySignatures.push(
159+
createSignature('desc'),
160+
createSignature('descId'),
161+
)
162+
}
163+
}
164+
165+
const prop = t.objectPattern(properties)
140166
props.push(prop)
141167
if (opts.typescript) {
142168
interfaces.push(
143169
t.tsInterfaceDeclaration(
144170
t.identifier('SVGRProps'),
145171
null,
146172
null,
147-
t.tSInterfaceBody([
148-
tsOptionalPropertySignature(
149-
t.identifier('title'),
150-
t.tsTypeAnnotation(t.tsStringKeyword()),
151-
),
152-
tsOptionalPropertySignature(
153-
t.identifier('titleId'),
154-
t.tsTypeAnnotation(t.tsStringKeyword()),
155-
),
156-
]),
173+
t.tSInterfaceBody(propertySignatures),
157174
),
158175
)
159176
prop.typeAnnotation = t.tsTypeAnnotation(

‎packages/babel-preset/src/index.test.ts

+49
Original file line numberDiff line numberDiff line change
@@ -83,6 +83,55 @@ describe('preset', () => {
8383
`)
8484
})
8585

86+
it('handles descProp', () => {
87+
expect(
88+
testPreset('<svg></svg>', {
89+
descProp: true,
90+
}),
91+
).toMatchInlineSnapshot(`
92+
"import * as React from \\"react\\";
93+
94+
const SvgComponent = ({
95+
desc,
96+
descId
97+
}) => <svg aria-describedby={descId}>{desc ? <desc id={descId}>{desc}</desc> : null}</svg>;
98+
99+
export default SvgComponent;"
100+
`)
101+
})
102+
it('handles descProp and fallback on existing desc', () => {
103+
// testing when existing desc has string as chilren
104+
expect(
105+
testPreset(`<svg><desc>Hello</desc></svg>`, {
106+
descProp: true,
107+
}),
108+
).toMatchInlineSnapshot(`
109+
"import * as React from \\"react\\";
110+
111+
const SvgComponent = ({
112+
desc,
113+
descId
114+
}) => <svg aria-describedby={descId}>{desc === undefined ? <desc id={descId}>Hello</desc> : desc ? <desc id={descId}>{desc}</desc> : null}</svg>;
115+
116+
export default SvgComponent;"
117+
`)
118+
// testing when existing desc has JSXExpression as children
119+
expect(
120+
testPreset(`<svg><desc>{"Hello"}</desc></svg>`, {
121+
descProp: true,
122+
}),
123+
).toMatchInlineSnapshot(`
124+
"import * as React from \\"react\\";
125+
126+
const SvgComponent = ({
127+
desc,
128+
descId
129+
}) => <svg aria-describedby={descId}>{desc === undefined ? <desc id={descId}>{\\"Hello\\"}</desc> : desc ? <desc id={descId}>{desc}</desc> : null}</svg>;
130+
131+
export default SvgComponent;"
132+
`)
133+
})
134+
86135
it('handles replaceAttrValues', () => {
87136
expect(
88137
testPreset('<svg a="#000" b="#fff" />', {

‎packages/babel-preset/src/index.ts

+16
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@ import transformSvgComponent, {
1818
export interface Options extends TransformOptions {
1919
ref?: boolean
2020
titleProp?: boolean
21+
descProp?: boolean
2122
expandProps?: boolean | 'start' | 'end'
2223
dimensions?: boolean
2324
icon?: boolean | string | number
@@ -76,6 +77,17 @@ const plugin = (_: ConfigAPI, opts: Options) => {
7677
]
7778
}
7879

80+
if (opts.descProp) {
81+
toAddAttributes = [
82+
...toAddAttributes,
83+
{
84+
name: 'aria-describedby',
85+
value: 'descId',
86+
literal: true,
87+
},
88+
]
89+
}
90+
7991
if (opts.expandProps) {
8092
toAddAttributes = [
8193
...toAddAttributes,
@@ -130,6 +142,10 @@ const plugin = (_: ConfigAPI, opts: Options) => {
130142
plugins.push(svgDynamicTitle)
131143
}
132144

145+
if (opts.descProp) {
146+
plugins.push([svgDynamicTitle, { tag: 'desc' }])
147+
}
148+
133149
if (opts.native) {
134150
plugins.push(transformReactNativeSVG)
135151
}

‎packages/cli/README.md

+1
Original file line numberDiff line numberDiff line change
@@ -35,6 +35,7 @@ Options:
3535
--index-template <file> specify a custom index.js template to use
3636
--no-index disable index file generation
3737
--title-prop create a title element linked with props
38+
--desc-prop create a desc element linked with props
3839
--prettier-config <fileOrJson> Prettier config
3940
--no-prettier disable Prettier
4041
--svgo-config <fileOrJson> SVGO config

‎packages/cli/src/__snapshots__/index.test.ts.snap

+52
Original file line numberDiff line numberDiff line change
@@ -155,6 +155,27 @@ export default SvgFile
155155
"
156156
`;
157157

158+
exports[`cli should support various args: --desc-prop 1`] = `
159+
"import * as React from 'react'
160+
161+
const SvgFile = ({ desc, descId, ...props }) => (
162+
<svg
163+
width={48}
164+
height={1}
165+
xmlns=\\"http://www.w3.org/2000/svg\\"
166+
aria-describedby={descId}
167+
{...props}
168+
>
169+
{desc ? <desc id={descId}>{desc}</desc> : null}
170+
<path d=\\"M0 0h48v1H0z\\" fill=\\"#063855\\" fillRule=\\"evenodd\\" />
171+
</svg>
172+
)
173+
174+
export default SvgFile
175+
176+
"
177+
`;
178+
158179
exports[`cli should support various args: --expand-props none 1`] = `
159180
"import * as React from 'react'
160181
@@ -484,6 +505,37 @@ export default SvgFile
484505
"
485506
`;
486507

508+
exports[`cli should support various args: --typescript --ref --desc-prop 1`] = `
509+
"import * as React from 'react'
510+
import { SVGProps, Ref, forwardRef } from 'react'
511+
interface SVGRProps {
512+
desc?: string;
513+
descId?: string;
514+
}
515+
516+
const SvgFile = (
517+
{ desc, descId, ...props }: SVGProps<SVGSVGElement> & SVGRProps,
518+
ref: Ref<SVGSVGElement>,
519+
) => (
520+
<svg
521+
width={48}
522+
height={1}
523+
xmlns=\\"http://www.w3.org/2000/svg\\"
524+
ref={ref}
525+
aria-describedby={descId}
526+
{...props}
527+
>
528+
{desc ? <desc id={descId}>{desc}</desc> : null}
529+
<path d=\\"M0 0h48v1H0z\\" fill=\\"#063855\\" fillRule=\\"evenodd\\" />
530+
</svg>
531+
)
532+
533+
const ForwardRef = forwardRef(SvgFile)
534+
export default ForwardRef
535+
536+
"
537+
`;
538+
487539
exports[`cli should support various args: --typescript --ref --title-prop 1`] = `
488540
"import * as React from 'react'
489541
import { SVGProps, Ref, forwardRef } from 'react'

‎packages/cli/src/index.test.ts

+2
Original file line numberDiff line numberDiff line change
@@ -131,9 +131,11 @@ describe('cli', () => {
131131
['--no-svgo'],
132132
['--no-prettier'],
133133
['--title-prop'],
134+
['--desc-prop'],
134135
['--typescript'],
135136
['--typescript --ref'],
136137
['--typescript --ref --title-prop'],
138+
['--typescript --ref --desc-prop'],
137139
])(
138140
'should support various args',
139141
async (args) => {

‎packages/cli/src/index.ts

+1
Original file line numberDiff line numberDiff line change
@@ -146,6 +146,7 @@ program
146146
)
147147
.option('--no-index', 'disable index file generation')
148148
.option('--title-prop', 'create a title element linked with props')
149+
.option('--desc-prop', 'create a desc element linked with props')
149150
.option(
150151
'--prettier-config <fileOrJson>',
151152
'Prettier config',

‎packages/core/src/__snapshots__/config.test.ts.snap

+8
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22

33
exports[`svgo async #loadConfig [async] should load config using filePath 1`] = `
44
Object {
5+
"descProp": false,
56
"dimensions": true,
67
"expandProps": "end",
78
"exportType": "default",
@@ -32,6 +33,7 @@ Object {
3233

3334
exports[`svgo async #loadConfig [async] should not load config with "runtimeConfig: false 1`] = `
3435
Object {
36+
"descProp": false,
3537
"dimensions": true,
3638
"expandProps": "end",
3739
"exportType": "default",
@@ -63,6 +65,7 @@ Object {
6365

6466
exports[`svgo async #loadConfig [async] should use default config without state.filePath 1`] = `
6567
Object {
68+
"descProp": false,
6669
"dimensions": false,
6770
"expandProps": "end",
6871
"exportType": "default",
@@ -87,6 +90,7 @@ Object {
8790

8891
exports[`svgo async #loadConfig [async] should work with custom config path 1`] = `
8992
Object {
93+
"descProp": false,
9094
"dimensions": true,
9195
"expandProps": "end",
9296
"exportType": "default",
@@ -117,6 +121,7 @@ Object {
117121

118122
exports[`svgo sync #loadConfig [sync] should load config using filePath 1`] = `
119123
Object {
124+
"descProp": false,
120125
"dimensions": true,
121126
"expandProps": "end",
122127
"exportType": "default",
@@ -147,6 +152,7 @@ Object {
147152

148153
exports[`svgo sync #loadConfig [sync] should not load config with "runtimeConfig: false 1`] = `
149154
Object {
155+
"descProp": false,
150156
"dimensions": true,
151157
"expandProps": "end",
152158
"exportType": "default",
@@ -178,6 +184,7 @@ Object {
178184

179185
exports[`svgo sync #loadConfig [sync] should use default config without state.filePath 1`] = `
180186
Object {
187+
"descProp": false,
181188
"dimensions": false,
182189
"expandProps": "end",
183190
"exportType": "default",
@@ -202,6 +209,7 @@ Object {
202209

203210
exports[`svgo sync #loadConfig [sync] should work with custom config path 1`] = `
204211
Object {
212+
"descProp": false,
205213
"dimensions": true,
206214
"expandProps": "end",
207215
"exportType": "default",

‎packages/core/src/__snapshots__/transform.test.ts.snap

+50
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,33 @@
11
// Jest Snapshot v1, https://goo.gl/fbAQLP
22

3+
exports[`convert config accepts options {"descProp":true} 1`] = `
4+
"import * as React from 'react'
5+
6+
const SvgComponent = ({ desc, descId, ...props }) => (
7+
<svg
8+
width={88}
9+
height={88}
10+
xmlns=\\"http://www.w3.org/2000/svg\\"
11+
aria-describedby={descId}
12+
{...props}
13+
>
14+
{desc ? <desc id={descId}>{desc}</desc> : null}
15+
<g
16+
stroke=\\"#063855\\"
17+
strokeWidth={2}
18+
fill=\\"none\\"
19+
fillRule=\\"evenodd\\"
20+
strokeLinecap=\\"square\\"
21+
>
22+
<path d=\\"M51 37 37 51M51 51 37 37\\" />
23+
</g>
24+
</svg>
25+
)
26+
27+
export default SvgComponent
28+
"
29+
`;
30+
331
exports[`convert config accepts options {"dimensions":false} 1`] = `
432
"import * as React from 'react'
533
@@ -489,6 +517,28 @@ export default noop
489517
"
490518
`;
491519

520+
exports[`convert config descProp: without desc added 1`] = `
521+
"import * as React from 'react'
522+
523+
const SvgComponent = ({ desc, descId, ...props }) => (
524+
<svg
525+
width={0}
526+
height={0}
527+
style={{
528+
position: 'absolute',
529+
}}
530+
aria-describedby={descId}
531+
{...props}
532+
>
533+
{desc ? <desc id={descId}>{desc}</desc> : null}
534+
<path d=\\"M0 0h24v24H0z\\" fill=\\"none\\" />
535+
</svg>
536+
)
537+
538+
export default SvgComponent
539+
"
540+
`;
541+
492542
exports[`convert config titleProp: without title added 1`] = `
493543
"import * as React from 'react'
494544

‎packages/core/src/config.ts

+2
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@ import type { State } from './state'
99
export interface Config {
1010
ref?: boolean
1111
titleProp?: boolean
12+
descProp?: boolean
1213
expandProps?: boolean | 'start' | 'end'
1314
dimensions?: boolean
1415
icon?: boolean | string | number
@@ -59,6 +60,7 @@ export const DEFAULT_CONFIG: Config = {
5960
template: undefined,
6061
index: false,
6162
titleProp: false,
63+
descProp: false,
6264
runtimeConfig: true,
6365
namedExport: 'ReactComponent',
6466
exportType: 'default',

‎packages/core/src/transform.test.ts

+12
Original file line numberDiff line numberDiff line change
@@ -317,6 +317,7 @@ describe('convert', () => {
317317
tpl`const noop = () => null; export default noop;`,
318318
},
319319
{ titleProp: true },
320+
{ descProp: true },
320321
{ memo: true },
321322
{
322323
namedExport: 'Component',
@@ -340,5 +341,16 @@ describe('convert', () => {
340341
await convertWithAllPlugins(svg, { titleProp: true }),
341342
).toMatchSnapshot()
342343
})
344+
345+
it('descProp: without desc added', async () => {
346+
const svg = `
347+
<svg width="0" height="0" style="position:absolute">
348+
<path d="M0 0h24v24H0z" fill="none" />
349+
</svg>
350+
`
351+
expect(
352+
await convertWithAllPlugins(svg, { descProp: true }),
353+
).toMatchSnapshot()
354+
})
343355
})
344356
})

‎packages/plugin-jsx/src/index.ts

+1
Original file line numberDiff line numberDiff line change
@@ -38,6 +38,7 @@ const jsxPlugin: Plugin = (code, config, state) => {
3838
const svgPresetOptions: SvgrPresetOptions = {
3939
ref: config.ref,
4040
titleProp: config.titleProp,
41+
descProp: config.descProp,
4142
expandProps: config.expandProps,
4243
dimensions: config.dimensions,
4344
icon: config.icon,

‎website/pages/docs/options.mdx

+8
Original file line numberDiff line numberDiff line change
@@ -178,6 +178,14 @@ Add title tag via title property. If titleProp is set to true and no title is pr
178178
| ------- | -------------- | -------------------- |
179179
| `false` | `--title-prop` | `titleProp: boolean` |
180180

181+
## Description
182+
183+
Add desc tag via desc property. If descProp is set to true and no description is provided (`desc={undefined}`) at render time, this will fallback to an existing desc element in the svg if exists.
184+
185+
| Default | CLI Override | API Override |
186+
| ------- | ------------- | ------------------- |
187+
| `false` | `--desc-prop` | `descProp: boolean` |
188+
181189
## Template
182190

183191
Specify a template file (CLI) or a template function (API) to use. For an example of template, see [the default one](https://github.com/gregberge/svgr/blob/main/packages/babel-plugin-transform-svg-component/src/defaultTemplate.ts).

‎website/src/components/playground/config/settings.js

+7
Original file line numberDiff line numberDiff line change
@@ -67,6 +67,13 @@ export const settings = [
6767
group: 'global',
6868
default: false,
6969
},
70+
{
71+
label: 'Desc prop',
72+
name: 'descProp',
73+
type: 'boolean',
74+
group: 'global',
75+
default: false,
76+
},
7077
{
7178
label: 'Expand props',
7279
name: 'expandProps',

1 commit comments

Comments
 (1)

vercel[bot] commented on Jun 19, 2022

@vercel[bot]

Successfully deployed to the following URLs:

svgr – ./

svgr-gregberge.vercel.app
svgr-git-main-gregberge.vercel.app
api.react-svgr.com

Please sign in to comment.