diff --git a/web/modals/threads/subchannels/subchannel.react.js b/web/modals/threads/subchannels/subchannel.react.js
new file mode 100644
--- /dev/null
+++ b/web/modals/threads/subchannels/subchannel.react.js
@@ -0,0 +1,75 @@
+// @flow
+
+import * as React from 'react';
+
+import { type ChatThreadItem } from 'lib/selectors/chat-selectors';
+import { getMessagePreview } from 'lib/shared/message-utils';
+import { shortAbsoluteDate } from 'lib/utils/date-utils';
+
+import { getDefaultTextMessageRules } from '../../../markdown/rules.react';
+import { useSelector } from '../../../redux/redux-utils';
+import { useOnClickThread } from '../../../selectors/nav-selectors';
+import SWMansionIcon from '../../../SWMansionIcon.react';
+import { useModalContext } from '../../modal-provider.react';
+import css from './subchannels-modal.css';
+
+type Props = {
+ +chatThreadItem: ChatThreadItem,
+};
+
+function Subchannel(props: Props): React.Node {
+ const { chatThreadItem } = props;
+ const {
+ threadInfo,
+ mostRecentMessageInfo,
+ lastUpdatedTimeIncludingSidebars,
+ } = chatThreadItem;
+
+ const timeZone = useSelector(state => state.timeZone);
+ const { clearModal } = useModalContext();
+
+ const navigateToThread = useOnClickThread(threadInfo);
+
+ const onClickThread = React.useCallback(
+ event => {
+ clearModal();
+ navigateToThread(event);
+ },
+ [clearModal, navigateToThread],
+ );
+
+ const lastActivity = React.useMemo(
+ () => shortAbsoluteDate(lastUpdatedTimeIncludingSidebars, timeZone),
+ [lastUpdatedTimeIncludingSidebars, timeZone],
+ );
+
+ const lastMessage = React.useMemo(() => {
+ if (!mostRecentMessageInfo) {
+ return
No messages
;
+ }
+ const { message, username } = getMessagePreview(
+ mostRecentMessageInfo,
+ threadInfo,
+ getDefaultTextMessageRules().simpleMarkdownRules,
+ );
+ const previewText = username ? `${username}: ${message}` : message;
+ return (
+ <>
+ {previewText}
+ {lastActivity}
+ >
+ );
+ }, [lastActivity, mostRecentMessageInfo, threadInfo]);
+
+ return (
+
+
+
+
{threadInfo.name}
+
{lastMessage}
+
+
+ );
+}
+
+export default Subchannel;
diff --git a/web/modals/threads/subchannels/subchannels-modal.css b/web/modals/threads/subchannels/subchannels-modal.css
new file mode 100644
--- /dev/null
+++ b/web/modals/threads/subchannels/subchannels-modal.css
@@ -0,0 +1,39 @@
+div.subchannelContainer {
+ cursor: pointer;
+ display: flex;
+ padding: 8px 16px;
+ column-gap: 8px;
+}
+
+div.subchannelContainer:hover {
+ color: var(--subchannels-modal-color-hover);
+}
+
+div.subchannelInfo {
+ flex: 1;
+ display: flex;
+ flex-direction: column;
+ row-gap: 8px;
+ overflow: hidden;
+}
+
+div.longTextEllipsis {
+ text-overflow: ellipsis;
+ overflow: hidden;
+ white-space: nowrap;
+}
+
+div.lastMessage {
+ display: flex;
+ justify-content: space-between;
+ column-gap: 14px;
+}
+
+div.noMessage {
+ text-align: center;
+ font-style: italic;
+}
+
+div.lastActivity {
+ white-space: nowrap;
+}
diff --git a/web/theme.css b/web/theme.css
--- a/web/theme.css
+++ b/web/theme.css
@@ -136,4 +136,6 @@
--members-modal-member-text-hover: var(--shades-white-100);
--label-default-bg: var(--violet-dark-80);
--label-default-color: var(--shades-white-80);
+ --subchannels-modal-color: var(--shades-black-60);
+ --subchannels-modal-color-hover: var(--shades-white-100);
}