Skip to content

Commit

Permalink
Add title to icon's <svg> as child tag in React, Vue.js and React Nat…
Browse files Browse the repository at this point in the history
…ive (#1147)

* Add title as child tag of <svg> in icons-react

* Add title as child tag of <svg> in icons-vue package

* Add title as child tag of <svg> in icons-react-native package

* Test <title> element in React

* Prevent adding <!----> to <svg> if no title prop passed

+ Test <title> child element.

* Fix tests for Vue

Stroke attribute added to expected SVG code.

---------

Co-authored-by: Bartłomiej Gawęda <[email protected]>
  • Loading branch information
BG-Software-BG and Bartłomiej Gawęda authored Jun 13, 2024
1 parent f8fb922 commit c898ade
Show file tree
Hide file tree
Showing 8 changed files with 39 additions and 10 deletions.
7 changes: 5 additions & 2 deletions packages/icons-react-native/src/createReactNativeComponent.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ const createReactNativeComponent = (
iconNode: IconNode,
): Icon => {
const Component = forwardRef<SVGSVGElement, IconProps>(
({ color = 'currentColor', size = 24, strokeWidth = 2, children, ...rest }: IconProps, ref) => {
({ color = 'currentColor', size = 24, strokeWidth = 2, title, children, ...rest }: IconProps, ref) => {
const customAttrs = {
stroke: color,
strokeWidth,
Expand All @@ -37,7 +37,10 @@ const createReactNativeComponent = (
{ ...childDefaultAttributes[type], ...customAttrs, ...attrs } as IconProps,
);
}),
...((Array.isArray(children) ? children : [children]) || []),
[
title && createElement('title', { key: 'svg-title' }, title),
...((Array.isArray(children) ? children : [children]) || [])
],
],
);
},
Expand Down
1 change: 1 addition & 0 deletions packages/icons-react-native/src/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ export type SVGAttributes = Partial<SVGProps<SVGSVGElement>>;
export interface IconProps extends SvgProps {
size?: string | number;
strokeWidth?: string | number;
title?: string;
}

export type Icon = ForwardRefExoticComponent<IconProps>;
3 changes: 2 additions & 1 deletion packages/icons-react/src/createReactComponent.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ const createReactComponent = (
) => {
const Component = forwardRef<Icon, IconProps>(
(
{ color = 'currentColor', size = 24, stroke = 2, className, children, ...rest }: IconProps,
{ color = 'currentColor', size = 24, stroke = 2, title, className, children, ...rest }: IconProps,
ref,
) =>
createElement(
Expand All @@ -32,6 +32,7 @@ const createReactComponent = (
...rest,
},
[
title && createElement('title', { key: 'svg-title' }, title),
...iconNode.map(([tag, attrs]) => createElement(tag, attrs)),
...(Array.isArray(children) ? children : [children]),
],
Expand Down
1 change: 1 addition & 0 deletions packages/icons-react/src/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ export type IconNode = [elementName: keyof ReactSVG, attrs: Record<string, strin
export interface IconProps extends Partial<Omit<React.ComponentPropsWithoutRef<'svg'>, 'stroke'>> {
size?: string | number;
stroke?: string | number;
title?: string;
}

export type Icon = FunctionComponent<IconProps>;
Expand Down
12 changes: 9 additions & 3 deletions packages/icons-react/test.spec.tsx
Original file line number Diff line number Diff line change
@@ -1,8 +1,6 @@
import { describe, it, expect, afterEach, assertType, expectTypeOf } from 'vitest';
import { describe, it, expect, afterEach, expectTypeOf } from 'vitest';
import { render, cleanup } from '@testing-library/react'
import { IconAccessible, IconAccessibleFilled, createReactComponent } from "./src/tabler-icons-react"
import type { IconNode } from './src/tabler-icons-react';
import { ReactNode } from 'react';

describe("React Icon component", () => {
afterEach(() => {
Expand Down Expand Up @@ -58,6 +56,14 @@ describe("React Icon component", () => {
expectTypeOf(IconAccessible).toEqualTypeOf(createReactComponent('outline', 'accessible', 'Accessible', []));
});

it('should add title child element to svg when title prop is passed', () => {
const { container } = render(<IconAccessible title="Accessible Icon"/>);
const svg = container.getElementsByTagName("svg")[0];
const title = svg.getElementsByTagName("title")[0];

expect(title).toHaveTextContent("Accessible Icon");
})

it("should match snapshot", () => {
const { container } = render(<IconAccessible/>)
expect(container.innerHTML).toMatchInlineSnapshot(`
Expand Down
6 changes: 4 additions & 2 deletions packages/icons-vue/src/createVueComponent.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,9 @@ const createVueComponent =
iconNamePascal: string,
iconNode: IconNode,
): Icon =>
({ size, color = 'currentColor', class: classes, stroke, ...rest }: IconProps, { attrs, slots }) => {
({ color = 'currentColor', size, stroke, title, class: classes, ...rest }: IconProps, { attrs, slots }) => {
let children = [...iconNode.map((child) => h(...child)), ...(slots.default ? [slots.default()] : [])];
if (title) children = [h('title', title), ...children];
return h(
'svg',
{
Expand All @@ -28,7 +30,7 @@ const createVueComponent =
}),
...rest,
},
[...iconNode.map((child) => h(...child)), ...(slots.default ? [slots.default()] : [])],
children,
);
};

Expand Down
1 change: 1 addition & 0 deletions packages/icons-vue/src/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,4 +11,5 @@ export type Icon = FunctionalComponent<SVGProps>;
export interface IconProps extends Partial<Omit<SVGProps, 'stroke'>> {
size?: string | number;
stroke?: string | number;
title?: string;
}
18 changes: 16 additions & 2 deletions packages/icons-vue/test.spec.js
Original file line number Diff line number Diff line change
Expand Up @@ -67,6 +67,20 @@ describe("Vue Icon component", () => {
expect(svg.getAttribute("stroke-width")).toBe(null)
})

it('should add title child element to svg when title prop is passed', () => {
const { container } = render(IconAccessible, {
props: {
title: 'Test Title',
}
})

const svg = container.getElementsByTagName("svg")[0]
const title = container.getElementsByTagName("title")[0]

expect(title).toHaveTextContent('Test Title')
expect(svg).toContainElement(title)
})

it('should call the onClick event', async () => {
const onClick = vi.fn()
const { container } = render(IconAccessible, {
Expand Down Expand Up @@ -97,11 +111,11 @@ describe("Vue Icon component", () => {
const textElement = getByText(testText)

expect(textElement).toBeInTheDocument()
expect(container.innerHTML).toMatchInlineSnapshot(`"<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="none" stroke-linecap="round" stroke-linejoin="round" class="tabler-icon tabler-icon-accessible"><path d="M12 12m-9 0a9 9 0 1 0 18 0a9 9 0 1 0 -18 0"></path><path d="M10 16.5l2 -3l2 3m-2 -3v-2l3 -1m-6 0l3 1"></path><circle cx="12" cy="7.5" r=".5" fill="currentColor"></circle><text>Hello World</text></svg>"`);
expect(container.innerHTML).toMatchInlineSnapshot(`"<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-linecap="round" stroke-linejoin="round" class="tabler-icon tabler-icon-accessible"><path d="M12 12m-9 0a9 9 0 1 0 18 0a9 9 0 1 0 -18 0"></path><path d="M10 16.5l2 -3l2 3m-2 -3v-2l3 -1m-6 0l3 1"></path><circle cx="12" cy="7.5" r=".5" fill="currentColor"></circle><text>Hello World</text></svg>"`);
});

it("should match snapshot", () => {
const { container } = render(IconAccessible);
expect(container.innerHTML).toMatchInlineSnapshot(`"<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="none" stroke-linecap="round" stroke-linejoin="round" class="tabler-icon tabler-icon-accessible"><path d="M12 12m-9 0a9 9 0 1 0 18 0a9 9 0 1 0 -18 0"></path><path d="M10 16.5l2 -3l2 3m-2 -3v-2l3 -1m-6 0l3 1"></path><circle cx="12" cy="7.5" r=".5" fill="currentColor"></circle></svg>"`)
expect(container.innerHTML).toMatchInlineSnapshot(`"<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-linecap="round" stroke-linejoin="round" class="tabler-icon tabler-icon-accessible"><path d="M12 12m-9 0a9 9 0 1 0 18 0a9 9 0 1 0 -18 0"></path><path d="M10 16.5l2 -3l2 3m-2 -3v-2l3 -1m-6 0l3 1"></path><circle cx="12" cy="7.5" r=".5" fill="currentColor"></circle></svg>"`)
})
})

0 comments on commit c898ade

Please sign in to comment.