<Icon />: Update to support @mdi/react instead of mdi-react (#37387)

This commit is contained in:
Tom Ross 2022-06-20 13:39:31 +01:00 committed by GitHub
parent 2a467db469
commit 8f02ed2871
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
7 changed files with 196 additions and 31 deletions

View File

@ -1,12 +1,14 @@
import { mdiClose } from '@mdi/js'
import { Story, Meta } from '@storybook/react'
import CloseIcon from 'mdi-react/CloseIcon'
import { BrandedStory } from '@sourcegraph/branded/src/components/BrandedStory'
import webStyles from '@sourcegraph/web/src/SourcegraphWebApp.scss'
import { H3 } from '..'
import { Icon } from '..'
import { H3 } from '../..'
import { SourcegraphIcon } from '../SourcegraphIcon'
import { Icon } from './Icon'
import { Code } from '../Typography'
const config: Meta = {
title: 'wildcard/Icon',
@ -39,5 +41,15 @@ export const Simple: Story = () => (
<H3>Medium Icon</H3>
<Icon as={SourcegraphIcon} size="md" aria-label="Sourcegraph logo" />
<H3>
Legacy <Code>mdi-react</Code> Icon
</H3>
<Icon as={CloseIcon} size="md" aria-label="Close" />
<H3>
New <Code>@mdi/react</Code> Icon
</H3>
<Icon svgPath={mdiClose} size="md" aria-label="Close" />
</>
)

View File

@ -1,16 +1,55 @@
import { mdiClose } from '@mdi/js'
import { render } from '@testing-library/react'
import CloseIcon from 'mdi-react/CloseIcon'
import { SourcegraphIcon } from '../SourcegraphIcon'
import { Icon } from './Icon'
describe('Icon', () => {
it('renders a simple inline icon correctly', () => {
const { asFragment } = render(<Icon as={SourcegraphIcon} aria-hidden={true} />)
expect(asFragment()).toMatchSnapshot()
describe('custom icons', () => {
it('renders a simple inline icon correctly', () => {
const { asFragment } = render(<Icon as={SourcegraphIcon} aria-hidden={true} />)
expect(asFragment()).toMatchSnapshot()
})
it('renders a medium icon correctly', () => {
const { asFragment } = render(<Icon as={SourcegraphIcon} size="md" aria-label="Sourcegraph logo" />)
expect(asFragment()).toMatchSnapshot()
})
})
it('renders a medium icon correctly', () => {
const { asFragment } = render(<Icon as={SourcegraphIcon} size="md" aria-hidden={true} />)
expect(asFragment()).toMatchSnapshot()
describe('legacy mdi-react icons', () => {
it('renders a simple inline icon correctly', () => {
const { asFragment } = render(<Icon as={CloseIcon} aria-hidden={true} />)
expect(asFragment()).toMatchSnapshot()
})
it('renders a medium icon correctly', () => {
const { asFragment } = render(<Icon as={CloseIcon} size="md" aria-label="Checkmark" />)
expect(asFragment()).toMatchSnapshot()
})
})
describe('new @mdi/react icons', () => {
it('renders a simple inline icon correctly', () => {
const { asFragment } = render(<Icon svgPath={mdiClose} aria-hidden={true} />)
expect(asFragment()).toMatchSnapshot()
})
it('renders a medium icon correctly', () => {
const { asFragment } = render(<Icon svgPath={mdiClose} size="md" aria-label="Checkmark" />)
expect(asFragment()).toMatchSnapshot()
})
})
describe('styled as icons', () => {
it('renders a simple span', () => {
const { asFragment } = render(
<Icon as="span" aria-label="Smile">
:)
</Icon>
)
expect(asFragment()).toMatchSnapshot()
})
})
})

View File

@ -1,5 +1,6 @@
import React, { ComponentType, ElementType, PropsWithChildren } from 'react'
import React, { AriaRole, ComponentType, ElementType } from 'react'
import MDIIcon from '@mdi/react'
import classNames from 'classnames'
import { MdiReactIconProps } from 'mdi-react'
@ -9,12 +10,19 @@ import { ICON_SIZES } from './constants'
import styles from './Icon.module.scss'
interface BaseIconProps extends Omit<MdiReactIconProps, 'children'> {
className?: string
interface BaseIconProps extends Omit<React.ComponentProps<typeof MDIIcon>, 'size' | 'path' | 'color'> {
/**
* Provide a custom `svgPath` to build an SVG.
*
* If using a Material Design icon, simply import the path from '@mdj/js'.
*/
svgPath?: string
/**
* The variant style of the icon. defaults to 'sm'
*/
size?: typeof ICON_SIZES[number]
className?: string
role?: AriaRole
}
interface ScreenReaderIconProps extends BaseIconProps {
@ -25,25 +33,33 @@ interface HiddenIconProps extends BaseIconProps {
'aria-hidden': true | 'true'
}
// We're currently migrating our icons to provide a descriptive label or use aria-hidden to be excluded from screen readers.
// Migration issue: https://github.com/sourcegraph/sourcegraph/issues/34582
// TODO: We should enforce that these props are provided once that migration is complete.
export type IconProps = HiddenIconProps | ScreenReaderIconProps
export const Icon = React.forwardRef((props, reference) => {
const { children, inline = true, className, size, as: Component = 'svg', role = 'img', ...attributes } = props
// eslint-disable-next-line react/display-name
export const Icon = React.forwardRef(({ children, className, size, ...props }, reference) => {
const iconStyle = classNames(styles.iconInline, size === 'md' && styles.iconInlineMd, className)
if (props.svgPath) {
const { svgPath, 'aria-label': ariaLabel, ...attributes } = props
return (
<MDIIcon
ref={reference as React.RefObject<SVGSVGElement>}
path={svgPath}
className={iconStyle}
title={ariaLabel}
{...attributes}
/>
)
}
const { as: IconComponent = 'svg', role = 'img', ...attributes } = props
return (
<Component
className={classNames(styles.iconInline, size === 'md' && styles.iconInlineMd, className)}
ref={reference}
role={role}
{...attributes}
>
<IconComponent ref={reference} className={iconStyle} role={role} {...attributes}>
{children}
</Component>
</IconComponent>
)
}) as ForwardReferenceComponent<
ComponentType<React.PropsWithChildren<MdiReactIconProps>> | ElementType,
PropsWithChildren<IconProps>
>
}) as ForwardReferenceComponent<ComponentType<React.PropsWithChildren<MdiReactIconProps>> | ElementType, IconProps>
Icon.displayName = 'Icon'

View File

@ -1,9 +1,9 @@
// Jest Snapshot v1, https://goo.gl/fbAQLP
exports[`Icon renders a medium icon correctly 1`] = `
exports[`Icon custom icons renders a medium icon correctly 1`] = `
<DocumentFragment>
<svg
aria-hidden="true"
aria-label="Sourcegraph logo"
class="iconInline iconInlineMd"
fill="none"
role="img"
@ -26,7 +26,7 @@ exports[`Icon renders a medium icon correctly 1`] = `
</DocumentFragment>
`;
exports[`Icon renders a simple inline icon correctly 1`] = `
exports[`Icon custom icons renders a simple inline icon correctly 1`] = `
<DocumentFragment>
<svg
aria-hidden="true"
@ -51,3 +51,87 @@ exports[`Icon renders a simple inline icon correctly 1`] = `
</svg>
</DocumentFragment>
`;
exports[`Icon legacy mdi-react icons renders a medium icon correctly 1`] = `
<DocumentFragment>
<svg
aria-label="Checkmark"
class="mdi-icon iconInline iconInlineMd"
fill="currentColor"
height="24"
role="img"
viewBox="0 0 24 24"
width="24"
>
<path
d="M19,6.41L17.59,5L12,10.59L6.41,5L5,6.41L10.59,12L5,17.59L6.41,19L12,13.41L17.59,19L19,17.59L13.41,12L19,6.41Z"
/>
</svg>
</DocumentFragment>
`;
exports[`Icon legacy mdi-react icons renders a simple inline icon correctly 1`] = `
<DocumentFragment>
<svg
aria-hidden="true"
class="mdi-icon iconInline"
fill="currentColor"
height="24"
role="img"
viewBox="0 0 24 24"
width="24"
>
<path
d="M19,6.41L17.59,5L12,10.59L6.41,5L5,6.41L10.59,12L5,17.59L6.41,19L12,13.41L17.59,19L19,17.59L13.41,12L19,6.41Z"
/>
</svg>
</DocumentFragment>
`;
exports[`Icon new @mdi/react icons renders a medium icon correctly 1`] = `
<DocumentFragment>
<svg
aria-labelledby="icon_labelledby_2"
class="iconInline iconInlineMd"
viewBox="0 0 24 24"
>
<title
id="icon_labelledby_2"
>
Checkmark
</title>
<path
d="M19,6.41L17.59,5L12,10.59L6.41,5L5,6.41L10.59,12L5,17.59L6.41,19L12,13.41L17.59,19L19,17.59L13.41,12L19,6.41Z"
style="fill: currentColor;"
/>
</svg>
</DocumentFragment>
`;
exports[`Icon new @mdi/react icons renders a simple inline icon correctly 1`] = `
<DocumentFragment>
<svg
aria-hidden="true"
class="iconInline"
role="presentation"
viewBox="0 0 24 24"
>
<path
d="M19,6.41L17.59,5L12,10.59L6.41,5L5,6.41L10.59,12L5,17.59L6.41,19L12,13.41L17.59,19L19,17.59L13.41,12L19,6.41Z"
style="fill: currentColor;"
/>
</svg>
</DocumentFragment>
`;
exports[`Icon styled as icons renders a simple span 1`] = `
<DocumentFragment>
<span
aria-label="Smile"
class="iconInline"
role="img"
>
:)
</span>
</DocumentFragment>
`;

View File

@ -362,6 +362,8 @@
"@codemirror/state": "^0.20.0",
"@codemirror/view": "^0.20.4",
"@lezer/highlight": "^0.16.0",
"@mdi/js": "^6.7.96",
"@mdi/react": "^1.6.0",
"@microsoft/fetch-event-source": "^2.0.1",
"@radix-ui/react-tooltip": "^0.1.8 || 0.1.8-rc.2",
"@reach/accordion": "^0.16.1",

View File

@ -2864,6 +2864,18 @@
find-up "^4.1.0"
fs-extra "^8.1.0"
"@mdi/js@^6.7.96":
version "6.7.96"
resolved "https://registry.npmjs.org/@mdi/js/-/js-6.7.96.tgz#6cd13ba64eeaf347c857feb3c328d31af3e75876"
integrity sha512-vZvhFrNN9LQx+Awu3nU6ESfXXDpRA/CA4mwikzU5g8uf9NpAocK43ecQvVNntwiXlLKpyplas8d4lsfpqjtXLA==
"@mdi/react@^1.6.0":
version "1.6.0"
resolved "https://registry.npmjs.org/@mdi/react/-/react-1.6.0.tgz#09b470c6b444a626206a1bd8901abcecf56bb3eb"
integrity sha512-FdeJ3Ee+m9nbBkZXW0XgSkDvevgOCloLRFrBlF6pRGWgT8v2U1M2aeXi8+uRM5FZ58o3dfVFduAtmCq7quclNA==
dependencies:
prop-types "^15.7.2"
"@mdn/browser-compat-data@2.0.7":
version "2.0.7"
resolved "https://registry.npmjs.org/@mdn/browser-compat-data/-/browser-compat-data-2.0.7.tgz#72ec37b9c1e00ce0b4e0309d753be18e2da12ee3"