diff --git a/web/apps/app-listing.react.js b/web/apps/app-listing.react.js
new file mode 100644
index 000000000..7fb5a5df7
--- /dev/null
+++ b/web/apps/app-listing.react.js
@@ -0,0 +1,83 @@
+// @flow
+
+import { faCheckCircle } from '@fortawesome/free-regular-svg-icons';
+import { faPlusCircle } from '@fortawesome/free-solid-svg-icons';
+import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
+import classnames from 'classnames';
+import * as React from 'react';
+import { useDispatch } from 'react-redux';
+
+import {
+ disableAppActionType,
+ enableAppActionType,
+} from 'lib/reducers/enabled-apps-reducer';
+import type { SupportedApps } from 'lib/types/enabled-apps';
+
+import SWMansionIcon from '../SWMansionIcon.react';
+import css from './apps.css';
+
+type Props = {
+ +id: SupportedApps | 'chat',
+ +readOnly: boolean,
+ +enabled: boolean,
+ +name: string,
+ +icon: 'message-square' | 'calendar',
+ +copy: string,
+};
+
+function AppListing(props: Props): React.Node {
+ const { id, readOnly, enabled, name, icon, copy } = props;
+ const dispatch = useDispatch();
+
+ const switchAppState = React.useCallback(
+ () =>
+ dispatch({
+ type: enabled ? disableAppActionType : enableAppActionType,
+ payload: id,
+ }),
+ [dispatch, enabled, id],
+ );
+
+ const actionButton = React.useMemo(() => {
+ const switchIcon = enabled ? faCheckCircle : faPlusCircle;
+ if (readOnly) {
+ const readOnlyIconClasses = classnames(
+ css.appListingIcon,
+ css.appListingIconState,
+ css.iconReadOnly,
+ );
+ return (
+
+
+
+ );
+ }
+ const iconClasses = classnames(
+ css.appListingIcon,
+ css.appListingIconState,
+ {
+ [css.iconEnabled]: enabled,
+ [css.iconDisabled]: !enabled,
+ },
+ );
+ return (
+
+
+
+ );
+ }, [enabled, readOnly, switchAppState]);
+ return (
+
+
+
+
+
+
{name}
+ {copy}
+
+ {actionButton}
+
+ );
+}
+
+export default AppListing;
diff --git a/web/apps/apps-directory.react.js b/web/apps/apps-directory.react.js
index ea3508abd..d1d2d9f13 100644
--- a/web/apps/apps-directory.react.js
+++ b/web/apps/apps-directory.react.js
@@ -1,9 +1,56 @@
// @flow
import * as React from 'react';
+import { useSelector } from 'react-redux';
+
+import AppListing from './app-listing.react';
+import css from './apps.css';
+
+const APP_DIRECTORY_DATA = [
+ {
+ id: 'chat',
+ defaultEnabled: true,
+ readOnly: true,
+ name: 'Chat',
+ icon: 'message-square',
+ copy: 'Keep in touch with your community',
+ },
+ {
+ id: 'calendar',
+ defaultEnabled: true,
+ readOnly: false,
+ name: 'Calendar',
+ icon: 'calendar',
+ copy: 'Shared calendar for your community',
+ },
+];
function AppsDirectory(): React.Node {
- return Apps directory
;
+ const enabledApps = useSelector(state => state.enabledApps);
+
+ const appData = React.useMemo(
+ () =>
+ APP_DIRECTORY_DATA.map(app => {
+ const { defaultEnabled, ...data } = app;
+ return {
+ ...data,
+ enabled: enabledApps[app.id] ?? defaultEnabled,
+ };
+ }),
+ [enabledApps],
+ );
+
+ const appItems = React.useMemo(
+ () => appData.map(item => ),
+ [appData],
+ );
+
+ return (
+
+
Choose Apps
+
{appItems}
+
+ );
}
export default AppsDirectory;
diff --git a/web/apps/apps.css b/web/apps/apps.css
new file mode 100644
index 000000000..13bc7b856
--- /dev/null
+++ b/web/apps/apps.css
@@ -0,0 +1,63 @@
+div.appsDirectoryContainer {
+ display: flex;
+ flex-direction: column;
+ align-items: flex-start;
+}
+
+h4.appsHeader {
+ color: var(--fg);
+ padding: 20px 0px 40px 40px;
+ font-weight: var(--semi-bold);
+}
+
+div.appsDirectoryList {
+ margin-left: 20px;
+ display: flex;
+ flex-direction: column;
+ row-gap: 10px;
+}
+
+div.appListingContainer {
+ color: var(--fg);
+ display: flex;
+ flex-direction: row;
+ align-items: center;
+}
+
+div.appListingTextContainer {
+ display: flex;
+ flex-direction: column;
+ flex: 1;
+}
+
+h5.appName {
+ font-weight: var(--semi-bold);
+ margin-bottom: 4px;
+}
+
+small.appCopy {
+ font-size: var(--xs-font-12);
+}
+
+div.appListingIcon {
+ padding: 0 20px;
+ align-self: stretch;
+ display: flex;
+ align-items: center;
+}
+
+div.appListingIconState {
+ font-size: var(--xl-font-20);
+}
+
+div.iconReadOnly {
+ color: var(--app-list-icon-read-only-color);
+}
+
+div.iconEnabled {
+ color: var(--app-list-icon-enabled-color);
+}
+
+div.iconDisabled {
+ color: var(--app-list-icon-disabled-color);
+}
diff --git a/web/theme.css b/web/theme.css
index 0111cec35..5c9aa732d 100644
--- a/web/theme.css
+++ b/web/theme.css
@@ -1,111 +1,114 @@
:root {
/* Never use color values defined here directly in CSS. Add color variables to "Color Theme" below
The reason we never use color values defined here directly in CSS is
1. It makes changing themes from light / dark / user generated impossible.
2. Gives the programmer context into the color being used.
3. If our color system changes it's much easier to change color values in one place.
Add a color value to the theme below, and then use it in your CSS.
naming convention:
- bg: background.
- fg: foreground.
- color: text-color
*/
--shades-white-100: #ffffff;
--shades-white-90: #f5f5f5;
--shades-white-80: #ebebeb;
--shades-white-70: #e0e0e0;
--shades-white-60: #cccccc;
--shades-black-100: #0a0a0a;
--shades-black-90: #1f1f1f;
--shades-black-80: #404040;
--shades-black-70: #666666;
--shades-black-60: #808080;
--violet-dark-100: #7e57c2;
--violet-dark-80: #6d49ab;
--violet-dark-60: #563894;
--violet-dark-40: #44297a;
--violet-dark-20: #331f5c;
--violet-light-100: #ae94db;
--violet-light-80: #b9a4df;
--violet-light-60: #d3c6ec;
--violet-light-40: #e8e0f5;
--violet-light-20: #f3f0fa;
--success-light-10: #d5f6e3;
--success-light-50: #6cdf9c;
--success-primary: #00c853;
--success-dark-50: #029841;
--success-dark-90: #034920;
--error-light-10: #feebe6;
--error-light-50: #f9947b;
--error-primary: #f53100;
--error-dark-50: #b62602;
--error-dark-90: #4f1203;
--bg: var(--shades-black-100);
--fg: var(--shades-white-100);
--color-disabled: var(--shades-black-60);
--text-input-bg: var(--shades-black-80);
--text-input-color: var(--shades-white-60);
--text-input-placeholder: var(--shades-white-60);
--border: var(--shades-black-80);
--error: var(--error-primary);
--success: var(--success-dark-50);
/* Color Theme */
--btn-bg-primary: var(--violet-dark-100);
--btn-bg-danger: var(--error-primary);
--chat-bg: var(--violet-dark-80);
--chat-confirmation-icon: var(--violet-dark-100);
--keyserver-selection: var(--violet-dark-60);
--thread-selection: var(--violet-light-80);
--selected-thread-bg: var(--shades-black-90);
--chat-timestamp-color: var(--shades-black-60);
--tool-tip-bg: var(--shades-black-80);
--tool-tip-color: var(--shades-white-60);
--border-color: var(--shades-black-80);
--calendar-chevron: var(--shades-black-60);
--calendar-day-bg: var(--shades-black-60);
--calendar-day-selected-color: var(--violet-dark-80);
--community-bg: var(--shades-black-90);
--unread-bg: var(--error-primary);
--settings-btn-bg: var(--violet-dark-100);
--modal-bg: var(--shades-black-90);
--join-bg: var(--shades-black-90);
--help-color: var(--shades-black-60);
--breadcrumb-color: var(--shades-black-60);
--breadcrumb-color-unread: var(--shades-white-60);
--btn-secondary-border: var(--shades-black-60);
--thread-color-read: var(--shades-black-60);
--thread-from-color-read: var(--shades-black-80);
--thread-last-message-color-read: var(--shades-black-60);
--relationship-button-green: var(--success-dark-50);
--relationship-button-red: var(--error-primary);
--relationship-button-text: var(--fg);
--disconnected-bar-alert-bg: var(--error-dark-50);
--disconnected-bar-alert-color: var(--shades-white-100);
--disconnected-bar-connecting-bg: var(--shades-white-70);
--disconnected-bar-connecting-color: var(--shades-black-100);
--permission-color: var(--shades-white-60);
--thread-top-bar-color: var(--shades-white-100);
--thread-top-bar-menu-color: var(--shades-white-70);
--thread-ancestor-keyserver-border: var(--shades-black-70);
--thread-ancestor-color-light: var(--shades-white-70);
--thread-ancestor-color-dark: var(--shades-black-100);
--thread-ancestor-separator-color: var(--shades-white-60);
--text-message-default-background: var(--shades-black-80);
--message-action-tooltip-bg: var(--shades-black-90);
--thread-menu-bg: var(--shades-black-90);
--thread-menu-separator-color: var(--shades-black-80);
--thread-menu-color: var(--shades-black-60);
--thread-menu-color-hover: var(--shades-white-100);
--thread-menu-color-dangerous: var(--error-primary);
--thread-menu-color-dangerous-hover: var(--error-light-50);
+ --app-list-icon-read-only-color: var(--shades-black-60);
+ --app-list-icon-enabled-color: var(--success-primary);
+ --app-list-icon-disabled-color: var(--shades-white-80);
}