diff --git a/frontend/src/Artist/Index/Overview/ArtistIndexOverviewInfoRow.tsx b/frontend/src/Artist/Index/Overview/ArtistIndexOverviewInfoRow.tsx
index 5d9b4a069..c44173595 100644
--- a/frontend/src/Artist/Index/Overview/ArtistIndexOverviewInfoRow.tsx
+++ b/frontend/src/Artist/Index/Overview/ArtistIndexOverviewInfoRow.tsx
@@ -1,11 +1,10 @@
-import { IconDefinition } from '@fortawesome/free-regular-svg-icons';
import React from 'react';
-import Icon from 'Components/Icon';
+import Icon, { IconProps } from 'Components/Icon';
import styles from './ArtistIndexOverviewInfoRow.css';
interface ArtistIndexOverviewInfoRowProps {
title?: string;
- iconName?: IconDefinition;
+ iconName: IconProps['name'];
label: string | null;
}
diff --git a/frontend/src/Components/Alert.js b/frontend/src/Components/Alert.js
deleted file mode 100644
index 418cbf5e6..000000000
--- a/frontend/src/Components/Alert.js
+++ /dev/null
@@ -1,34 +0,0 @@
-import classNames from 'classnames';
-import PropTypes from 'prop-types';
-import React from 'react';
-import { kinds } from 'Helpers/Props';
-import styles from './Alert.css';
-
-function Alert(props) {
- const { className, kind, children, ...otherProps } = props;
-
- return (
-
- {children}
-
- );
-}
-
-Alert.propTypes = {
- className: PropTypes.string,
- kind: PropTypes.oneOf(kinds.all),
- children: PropTypes.node.isRequired
-};
-
-Alert.defaultProps = {
- className: styles.alert,
- kind: kinds.INFO
-};
-
-export default Alert;
diff --git a/frontend/src/Components/Alert.tsx b/frontend/src/Components/Alert.tsx
new file mode 100644
index 000000000..92c89e741
--- /dev/null
+++ b/frontend/src/Components/Alert.tsx
@@ -0,0 +1,18 @@
+import classNames from 'classnames';
+import React from 'react';
+import { Kind } from 'Helpers/Props/kinds';
+import styles from './Alert.css';
+
+interface AlertProps {
+ className?: string;
+ kind?: Extract;
+ children: React.ReactNode;
+}
+
+function Alert(props: AlertProps) {
+ const { className = styles.alert, kind = 'info', children } = props;
+
+ return {children}
;
+}
+
+export default Alert;
diff --git a/frontend/src/Components/Card.js b/frontend/src/Components/Card.js
deleted file mode 100644
index c5a4d164c..000000000
--- a/frontend/src/Components/Card.js
+++ /dev/null
@@ -1,60 +0,0 @@
-import PropTypes from 'prop-types';
-import React, { Component } from 'react';
-import Link from 'Components/Link/Link';
-import styles from './Card.css';
-
-class Card extends Component {
-
- //
- // Render
-
- render() {
- const {
- className,
- overlayClassName,
- overlayContent,
- children,
- onPress
- } = this.props;
-
- if (overlayContent) {
- return (
-
- );
- }
-
- return (
-
- {children}
-
- );
- }
-}
-
-Card.propTypes = {
- className: PropTypes.string.isRequired,
- overlayClassName: PropTypes.string.isRequired,
- overlayContent: PropTypes.bool.isRequired,
- children: PropTypes.node.isRequired,
- onPress: PropTypes.func.isRequired
-};
-
-Card.defaultProps = {
- className: styles.card,
- overlayClassName: styles.overlay,
- overlayContent: false
-};
-
-export default Card;
diff --git a/frontend/src/Components/Card.tsx b/frontend/src/Components/Card.tsx
new file mode 100644
index 000000000..24588c841
--- /dev/null
+++ b/frontend/src/Components/Card.tsx
@@ -0,0 +1,39 @@
+import React from 'react';
+import Link, { LinkProps } from 'Components/Link/Link';
+import styles from './Card.css';
+
+interface CardProps extends Pick {
+ // TODO: Consider using different properties for classname depending if it's overlaying content or not
+ className?: string;
+ overlayClassName?: string;
+ overlayContent?: boolean;
+ children: React.ReactNode;
+}
+
+function Card(props: CardProps) {
+ const {
+ className = styles.card,
+ overlayClassName = styles.overlay,
+ overlayContent = false,
+ children,
+ onPress,
+ } = props;
+
+ if (overlayContent) {
+ return (
+
+ );
+ }
+
+ return (
+
+ {children}
+
+ );
+}
+
+export default Card;
diff --git a/frontend/src/Components/DragPreviewLayer.js b/frontend/src/Components/DragPreviewLayer.js
deleted file mode 100644
index a111df70e..000000000
--- a/frontend/src/Components/DragPreviewLayer.js
+++ /dev/null
@@ -1,22 +0,0 @@
-import PropTypes from 'prop-types';
-import React from 'react';
-import styles from './DragPreviewLayer.css';
-
-function DragPreviewLayer({ children, ...otherProps }) {
- return (
-
- {children}
-
- );
-}
-
-DragPreviewLayer.propTypes = {
- children: PropTypes.node,
- className: PropTypes.string
-};
-
-DragPreviewLayer.defaultProps = {
- className: styles.dragLayer
-};
-
-export default DragPreviewLayer;
diff --git a/frontend/src/Components/DragPreviewLayer.tsx b/frontend/src/Components/DragPreviewLayer.tsx
new file mode 100644
index 000000000..2e578504b
--- /dev/null
+++ b/frontend/src/Components/DragPreviewLayer.tsx
@@ -0,0 +1,21 @@
+import React from 'react';
+import styles from './DragPreviewLayer.css';
+
+interface DragPreviewLayerProps {
+ className?: string;
+ children?: React.ReactNode;
+}
+
+function DragPreviewLayer({
+ className = styles.dragLayer,
+ children,
+ ...otherProps
+}: DragPreviewLayerProps) {
+ return (
+
+ {children}
+
+ );
+}
+
+export default DragPreviewLayer;
diff --git a/frontend/src/Components/FieldSet.js b/frontend/src/Components/FieldSet.js
deleted file mode 100644
index 8243fd00c..000000000
--- a/frontend/src/Components/FieldSet.js
+++ /dev/null
@@ -1,41 +0,0 @@
-import classNames from 'classnames';
-import PropTypes from 'prop-types';
-import React, { Component } from 'react';
-import { sizes } from 'Helpers/Props';
-import styles from './FieldSet.css';
-
-class FieldSet extends Component {
-
- //
- // Render
-
- render() {
- const {
- size,
- legend,
- children
- } = this.props;
-
- return (
-
- );
- }
-
-}
-
-FieldSet.propTypes = {
- size: PropTypes.oneOf(sizes.all).isRequired,
- legend: PropTypes.oneOfType([PropTypes.node, PropTypes.string]),
- children: PropTypes.node
-};
-
-FieldSet.defaultProps = {
- size: sizes.MEDIUM
-};
-
-export default FieldSet;
diff --git a/frontend/src/Components/FieldSet.tsx b/frontend/src/Components/FieldSet.tsx
new file mode 100644
index 000000000..c2ff03a7f
--- /dev/null
+++ b/frontend/src/Components/FieldSet.tsx
@@ -0,0 +1,29 @@
+import classNames from 'classnames';
+import React, { ComponentProps } from 'react';
+import { sizes } from 'Helpers/Props';
+import { Size } from 'Helpers/Props/sizes';
+import styles from './FieldSet.css';
+
+interface FieldSetProps {
+ size?: Size;
+ legend?: ComponentProps<'legend'>['children'];
+ children?: React.ReactNode;
+}
+
+function FieldSet({ size = sizes.MEDIUM, legend, children }: FieldSetProps) {
+ return (
+
+ );
+}
+
+export default FieldSet;
diff --git a/frontend/src/Components/HeartRating.js b/frontend/src/Components/HeartRating.js
deleted file mode 100644
index fe53a4e5f..000000000
--- a/frontend/src/Components/HeartRating.js
+++ /dev/null
@@ -1,30 +0,0 @@
-import PropTypes from 'prop-types';
-import React from 'react';
-import Icon from 'Components/Icon';
-import { icons } from 'Helpers/Props';
-import styles from './HeartRating.css';
-
-function HeartRating({ rating, iconSize }) {
- return (
-
-
-
- {rating * 10}%
-
- );
-}
-
-HeartRating.propTypes = {
- rating: PropTypes.number.isRequired,
- iconSize: PropTypes.number.isRequired
-};
-
-HeartRating.defaultProps = {
- iconSize: 14
-};
-
-export default HeartRating;
diff --git a/frontend/src/Components/HeartRating.tsx b/frontend/src/Components/HeartRating.tsx
new file mode 100644
index 000000000..774cb4239
--- /dev/null
+++ b/frontend/src/Components/HeartRating.tsx
@@ -0,0 +1,30 @@
+import React from 'react';
+import Icon, { IconProps } from 'Components/Icon';
+import Tooltip from 'Components/Tooltip/Tooltip';
+import { icons, kinds, tooltipPositions } from 'Helpers/Props';
+import translate from 'Utilities/String/translate';
+import styles from './HeartRating.css';
+
+interface HeartRatingProps {
+ rating: number;
+ votes?: number;
+ iconSize?: IconProps['size'];
+}
+
+function HeartRating({ rating, votes = 0, iconSize = 14 }: HeartRatingProps) {
+ return (
+
+
+ {rating * 10}%
+
+ }
+ tooltip={translate('CountVotes', { votes })}
+ kind={kinds.INVERSE}
+ position={tooltipPositions.TOP}
+ />
+ );
+}
+
+export default HeartRating;
diff --git a/frontend/src/Components/Icon.js b/frontend/src/Components/Icon.js
deleted file mode 100644
index d200b8c08..000000000
--- a/frontend/src/Components/Icon.js
+++ /dev/null
@@ -1,73 +0,0 @@
-import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
-import classNames from 'classnames';
-import PropTypes from 'prop-types';
-import React, { PureComponent } from 'react';
-import { kinds } from 'Helpers/Props';
-import styles from './Icon.css';
-
-class Icon extends PureComponent {
-
- //
- // Render
-
- render() {
- const {
- containerClassName,
- className,
- name,
- kind,
- size,
- title,
- isSpinning,
- ...otherProps
- } = this.props;
-
- const icon = (
-
- );
-
- if (title) {
- return (
-
- {icon}
-
- );
- }
-
- return icon;
- }
-}
-
-Icon.propTypes = {
- containerClassName: PropTypes.string,
- className: PropTypes.string,
- name: PropTypes.object.isRequired,
- kind: PropTypes.string.isRequired,
- size: PropTypes.number.isRequired,
- title: PropTypes.oneOfType([PropTypes.string, PropTypes.func]),
- isSpinning: PropTypes.bool.isRequired,
- fixedWidth: PropTypes.bool.isRequired
-};
-
-Icon.defaultProps = {
- kind: kinds.DEFAULT,
- size: 14,
- isSpinning: false,
- fixedWidth: false
-};
-
-export default Icon;
diff --git a/frontend/src/Components/Icon.tsx b/frontend/src/Components/Icon.tsx
new file mode 100644
index 000000000..ea5279840
--- /dev/null
+++ b/frontend/src/Components/Icon.tsx
@@ -0,0 +1,60 @@
+import {
+ FontAwesomeIcon,
+ FontAwesomeIconProps,
+} from '@fortawesome/react-fontawesome';
+import classNames from 'classnames';
+import React, { ComponentProps } from 'react';
+import { kinds } from 'Helpers/Props';
+import { Kind } from 'Helpers/Props/kinds';
+import styles from './Icon.css';
+
+export interface IconProps
+ extends Omit<
+ FontAwesomeIconProps,
+ 'icon' | 'spin' | 'name' | 'title' | 'size'
+ > {
+ containerClassName?: ComponentProps<'span'>['className'];
+ name: FontAwesomeIconProps['icon'];
+ kind?: Extract;
+ size?: number;
+ isSpinning?: FontAwesomeIconProps['spin'];
+ title?: string | (() => string);
+}
+
+export default function Icon({
+ containerClassName,
+ className,
+ name,
+ kind = kinds.DEFAULT,
+ size = 14,
+ title,
+ isSpinning = false,
+ fixedWidth = false,
+ ...otherProps
+}: IconProps) {
+ const icon = (
+
+ );
+
+ if (title) {
+ return (
+
+ {icon}
+
+ );
+ }
+
+ return icon;
+}
diff --git a/frontend/src/System/Status/Health/Health.tsx b/frontend/src/System/Status/Health/Health.tsx
index 281d95ac6..087146740 100644
--- a/frontend/src/System/Status/Health/Health.tsx
+++ b/frontend/src/System/Status/Health/Health.tsx
@@ -3,7 +3,7 @@ import { useDispatch, useSelector } from 'react-redux';
import AppState from 'App/State/AppState';
import Alert from 'Components/Alert';
import FieldSet from 'Components/FieldSet';
-import Icon from 'Components/Icon';
+import Icon, { IconProps } from 'Components/Icon';
import IconButton from 'Components/Link/IconButton';
import SpinnerIconButton from 'Components/Link/SpinnerIconButton';
import LoadingIndicator from 'Components/Loading/LoadingIndicator';
@@ -97,7 +97,7 @@ function Health() {
{items.map((item) => {
const source = item.source;
- let kind = kinds.WARNING;
+ let kind: IconProps['kind'] = kinds.WARNING;
switch (item.type.toLowerCase()) {
case 'error':
kind = kinds.DANGER;
diff --git a/frontend/src/System/Tasks/Queued/QueuedTaskRow.tsx b/frontend/src/System/Tasks/Queued/QueuedTaskRow.tsx
index 4511bcbf4..66d762039 100644
--- a/frontend/src/System/Tasks/Queued/QueuedTaskRow.tsx
+++ b/frontend/src/System/Tasks/Queued/QueuedTaskRow.tsx
@@ -2,7 +2,7 @@ import moment from 'moment';
import React, { useCallback, useEffect, useRef, useState } from 'react';
import { useDispatch, useSelector } from 'react-redux';
import { CommandBody } from 'Commands/Command';
-import Icon from 'Components/Icon';
+import Icon, { IconProps } from 'Components/Icon';
import IconButton from 'Components/Link/IconButton';
import ConfirmModal from 'Components/Modal/ConfirmModal';
import TableRowCell from 'Components/Table/Cells/TableRowCell';
@@ -19,7 +19,10 @@ import translate from 'Utilities/String/translate';
import QueuedTaskRowNameCell from './QueuedTaskRowNameCell';
import styles from './QueuedTaskRow.css';
-function getStatusIconProps(status: string, message: string | undefined) {
+function getStatusIconProps(
+ status: string,
+ message: string | undefined
+): IconProps {
const title = titleCase(status);
switch (status) {