diff --git a/src/Icon/Icon.test.tsx b/src/Icon/Icon.test.tsx
index 43cf4ce0913..94b2ebe283c 100644
--- a/src/Icon/Icon.test.tsx
+++ b/src/Icon/Icon.test.tsx
@@ -42,11 +42,8 @@ describe('', () => {
{/* @ts-expect-error Using a non-existent icon from @openedx/paragon/icons is a type error */}
- {/* @ts-expect-error The 'src' prop cannot be a string. */}
- {/* @ts-expect-error Random props cannot be added */}
- {/* @ts-expect-error This is not a valid size property */}
;
});
diff --git a/src/Icon/index.d.ts b/src/Icon/index.d.ts
deleted file mode 100644
index b9d6f5d7468..00000000000
--- a/src/Icon/index.d.ts
+++ /dev/null
@@ -1,20 +0,0 @@
-import React from 'react';
-
-export interface IconProps extends React.ComponentPropsWithoutRef<'span'> {
- // Note: React.ComponentType is what we want here. React.ElementType would allow some element type strings like "div",
- // but we only want to allow components like 'Add' (a specific icon component function/class)
- src?: React.ComponentType;
- svgAttrs?: {
- 'aria-label'?: string;
- 'aria-labelledby'?: string;
- };
- id?: string | null;
- size?: 'xs' | 'sm' | 'md' | 'lg' | 'inline';
- className?: string | string[];
- hidden?: boolean;
- screenReaderText?: React.ReactNode;
-}
-
-declare const Icon: React.FC;
-
-export default Icon;
diff --git a/src/Icon/index.jsx b/src/Icon/index.tsx
similarity index 76%
rename from src/Icon/index.jsx
rename to src/Icon/index.tsx
index 89403430f25..f32de471bd6 100644
--- a/src/Icon/index.jsx
+++ b/src/Icon/index.tsx
@@ -1,5 +1,4 @@
import React from 'react';
-import PropTypes from 'prop-types';
import classNames from 'classnames';
import newId from '../utils/newId';
@@ -12,16 +11,53 @@ import withDeprecatedProps, { DeprTypes } from '../withDeprecatedProps';
* - focusable is set to false on the svg in all cases as a workaround for an ie11 bug
*/
+interface SvgAttrs extends React.SVGAttributes {
+ 'aria-label'?: string;
+ 'aria-labelledby'?: string;
+ 'aria-hidden'?: boolean;
+}
+
+export interface IconProps extends Omit, 'id' | 'className'> {
+ /**
+ * An icon component to render.
+ * Example import of a Paragon icon component: `import { Check } from '@openedx/paragon/icons';`
+ */
+ src?: React.ComponentType>;
+ /** HTML element attributes to pass through to the underlying svg element */
+ svgAttrs?: SvgAttrs;
+ /**
+ * the `id` property of the Icon element, by default this value is generated
+ * with the `newId` function with the `prefix` of `Icon`.
+ */
+ id?: string | null;
+ /** The size of the icon. */
+ size?: 'xs' | 'sm' | 'md' | 'lg' | 'inline';
+ /** A class name that will define what the Icon looks like. */
+ className?: string | string[];
+ /**
+ * a boolean that determines the value of `aria-hidden` attribute on the Icon span,
+ * this value is `true` by default.
+ */
+ hidden?: boolean;
+ /**
+ * a string or an element that will be used on a secondary span leveraging the `sr-only` style
+ * for screenreader only text, this value is `undefined` by default. This value is recommended for use unless
+ * the Icon is being used in a way that is purely decorative or provides no additional context for screen
+ * reader users. This field should be thought of the same way an `alt` attribute would be used for `image` tags.
+ */
+ screenReaderText?: React.ReactNode;
+}
+
function Icon({
src: Component,
id,
className,
- hidden,
+ hidden = true,
screenReaderText,
- svgAttrs,
+ svgAttrs = {},
size,
...attrs
-}) {
+}: IconProps) {
if (Component) {
// If no aria label is specified, hide this icon from screenreaders
const hasAriaLabel = svgAttrs['aria-label'] || svgAttrs['aria-labelledby'];
@@ -35,8 +71,8 @@ function Icon({
return (
{screenReaderText && (
@@ -69,55 +105,11 @@ function Icon({
);
}
-Icon.propTypes = {
- /**
- * An icon component to render.
- * Example import of a Paragon icon component: `import { Check } from '@openedx/paragon/icons';`
- */
- src: PropTypes.elementType,
- /** HTML element attributes to pass through to the underlying svg element */
- svgAttrs: PropTypes.shape({
- 'aria-label': PropTypes.string,
- 'aria-labelledby': PropTypes.string,
- }),
- /**
- * the `id` property of the Icon element, by default this value is generated
- * with the `newId` function with the `prefix` of `Icon`.
- */
- id: PropTypes.string,
- /** The size of the icon. */
- size: PropTypes.oneOf(['xs', 'sm', 'md', 'lg']),
- /** A class name that will define what the Icon looks like. */
- className: PropTypes.string,
- /**
- * a boolean that determines the value of `aria-hidden` attribute on the Icon span,
- * this value is `true` by default.
- */
- hidden: PropTypes.bool,
- /**
- * a string or an element that will be used on a secondary span leveraging the `sr-only` style
- * for screenreader only text, this value is `undefined` by default. This value is recommended for use unless
- * the Icon is being used in a way that is purely decorative or provides no additional context for screen
- * reader users. This field should be thought of the same way an `alt` attribute would be used for `image` tags.
- */
- screenReaderText: PropTypes.oneOfType([PropTypes.string, PropTypes.element]),
-};
-
-Icon.defaultProps = {
- src: null,
- svgAttrs: {},
- id: undefined,
- hidden: true,
- screenReaderText: undefined,
- size: undefined,
- className: undefined,
-};
-
export default withDeprecatedProps(Icon, 'Icon', {
className: {
deprType: DeprTypes.FORMAT,
- expect: value => typeof value === 'string',
- transform: value => (Array.isArray(value) ? value.join(' ') : value),
+ expect: (value: any) => typeof value === 'string',
+ transform: (value: any) => (Array.isArray(value) ? value.join(' ') : value),
message: 'It should be a string.',
},
});