diff --git a/client/app/components/cards-list/CardsList.jsx b/client/app/components/cards-list/CardsList.jsx
deleted file mode 100644
index 39a9747fad..0000000000
--- a/client/app/components/cards-list/CardsList.jsx
+++ /dev/null
@@ -1,84 +0,0 @@
-import Input from "antd/lib/input";
-import { includes, isEmpty } from "lodash";
-import PropTypes from "prop-types";
-import React from "react";
-import Link from "@/components/Link";
-import EmptyState from "@/components/items-list/components/EmptyState";
-
-import "./CardsList.less";
-
-const { Search } = Input;
-
-export default class CardsList extends React.Component {
- static propTypes = {
- items: PropTypes.arrayOf(
- PropTypes.shape({
- title: PropTypes.string.isRequired,
- imgSrc: PropTypes.string.isRequired,
- onClick: PropTypes.func,
- href: PropTypes.string,
- })
- ),
- showSearch: PropTypes.bool,
- };
-
- static defaultProps = {
- items: [],
- showSearch: false,
- };
-
- state = {
- searchText: "",
- };
-
- constructor(props) {
- super(props);
- this.items = [];
-
- let itemId = 1;
- props.items.forEach(item => {
- this.items.push({ id: itemId, ...item });
- itemId += 1;
- });
- }
-
- // eslint-disable-next-line class-methods-use-this
- renderListItem(item) {
- return (
-
-
-
{item.title}
-
- );
- }
-
- render() {
- const { showSearch } = this.props;
- const { searchText } = this.state;
-
- const filteredItems = this.items.filter(
- item => isEmpty(searchText) || includes(item.title.toLowerCase(), searchText.toLowerCase())
- );
-
- return (
-
- {showSearch && (
-
-
- this.setState({ searchText: e.target.value })} autoFocus />
-
-
- )}
- {isEmpty(filteredItems) ? (
-
- ) : (
-
-
- {filteredItems.map(item => this.renderListItem(item))}
-
-
- )}
-
- );
- }
-}
diff --git a/client/app/components/cards-list/CardsList.tsx b/client/app/components/cards-list/CardsList.tsx
new file mode 100644
index 0000000000..d3b305892b
--- /dev/null
+++ b/client/app/components/cards-list/CardsList.tsx
@@ -0,0 +1,80 @@
+import { includes, isEmpty } from "lodash";
+import PropTypes from "prop-types";
+import React, { useState } from "react";
+import Input from "antd/lib/input";
+import Link from "@/components/Link";
+import EmptyState from "@/components/items-list/components/EmptyState";
+
+import "./CardsList.less";
+
+export interface CardsListItem {
+ title: string;
+ imgSrc: string;
+ onClick?: () => void;
+ href?: string;
+}
+
+export interface CardsListProps {
+ items?: CardsListItem[];
+ showSearch?: boolean;
+}
+
+interface ListItemProps {
+ item: CardsListItem;
+ keySuffix: string;
+}
+
+function ListItem({ item, keySuffix }: ListItemProps) {
+ return (
+
+
+ {item.title}
+
+ );
+}
+
+export default function CardsList({ items = [], showSearch = false }: CardsListProps) {
+ const [searchText, setSearchText] = useState("");
+ const filteredItems = items.filter(
+ item => isEmpty(searchText) || includes(item.title.toLowerCase(), searchText.toLowerCase())
+ );
+
+ return (
+
+ {showSearch && (
+
+
+ ) => setSearchText(e.target.value)}
+ autoFocus
+ />
+
+
+ )}
+ {isEmpty(filteredItems) ? (
+
+ ) : (
+
+
+ {filteredItems.map((item: CardsListItem, index: number) => (
+
+ ))}
+
+
+ )}
+
+ );
+}
+
+CardsList.propTypes = {
+ items: PropTypes.arrayOf(
+ PropTypes.shape({
+ title: PropTypes.string.isRequired,
+ imgSrc: PropTypes.string.isRequired,
+ onClick: PropTypes.func,
+ href: PropTypes.string,
+ })
+ ),
+ showSearch: PropTypes.bool,
+};