diff --git a/web/components/community-actions-menu.react.js b/web/components/community-actions-menu.react.js index f6af2abf4..319f155f6 100644 --- a/web/components/community-actions-menu.react.js +++ b/web/components/community-actions-menu.react.js @@ -1,129 +1,133 @@ // @flow import * as React from 'react'; import { useModalContext } from 'lib/components/modal-provider.react.js'; import SWMansionIcon from 'lib/components/SWMansionIcon.react.js'; import { primaryInviteLinksSelector } from 'lib/selectors/invite-links-selectors.js'; import { threadInfoSelector } from 'lib/selectors/thread-selectors.js'; import { threadHasPermission } from 'lib/shared/thread-utils.js'; import type { InviteLink } from 'lib/types/link-types.js'; import { threadPermissions } from 'lib/types/thread-permission-types.js'; import css from './community-actions-menu.css'; import MenuItem from '../components/menu-item.react.js'; import Menu from '../components/menu.react.js'; import ManageInviteLinksModal from '../invite-links/manage-invite-links-modal.react.js'; import ViewInviteLinkModal from '../invite-links/view-invite-link-modal.react.js'; import { useSelector } from '../redux/redux-utils.js'; +import CommunityRolesModal from '../roles/community-roles-modal.react.js'; import { AddLink } from '../vectors.react.js'; type Props = { +communityID: string, }; function CommunityActionsMenu(props: Props): React.Node { const { communityID } = props; const inviteLink: ?InviteLink = useSelector(primaryInviteLinksSelector)[ communityID ]; const { pushModal } = useModalContext(); const community = useSelector( state => threadInfoSelector(state)[communityID], ); const canManageLinks = threadHasPermission( community, threadPermissions.MANAGE_INVITE_LINKS, ); const canChangeRoles = threadHasPermission( community, threadPermissions.CHANGE_ROLE, ); const openViewInviteLinkModal = React.useCallback(() => { if (!inviteLink) { return; } pushModal(); }, [inviteLink, pushModal]); const openManageInviteLinksModal = React.useCallback(() => { pushModal(); }, [communityID, pushModal]); - const openCommunityRolesModal = React.useCallback(() => {}, []); + const openCommunityRolesModal = React.useCallback( + () => pushModal(), + [community, pushModal], + ); const items = React.useMemo(() => { const itemSpecs = []; if (canManageLinks) { itemSpecs.push({ text: 'Manage invite links', iconComponent: , onClick: openManageInviteLinksModal, }); } if (inviteLink) { itemSpecs.push({ text: 'Invite link', icon: 'link', onClick: openViewInviteLinkModal, }); } if (canChangeRoles) { itemSpecs.push({ text: 'Roles', icon: 'user-info', onClick: openCommunityRolesModal, }); } return itemSpecs; }, [ canManageLinks, inviteLink, openManageInviteLinksModal, openViewInviteLinkModal, canChangeRoles, openCommunityRolesModal, ]); const menuItems = React.useMemo( () => items.map(item => { if (item.icon) { return ( ); } return ( ); }), [items], ); const icon = ; return (
{menuItems}
); } export default CommunityActionsMenu; diff --git a/web/roles/community-roles-modal.css b/web/roles/community-roles-modal.css new file mode 100644 index 000000000..bb2205dbd --- /dev/null +++ b/web/roles/community-roles-modal.css @@ -0,0 +1,52 @@ +.modalDescription { + color: var(--modal-fg); + padding: 16px 32px 0 32px; + font-size: var(--m-font-16); +} + +.rolePanelTitleContainer { + display: flex; + flex-direction: row; + justify-content: space-between; + margin: 32px 32px 16px 32px; +} + +.rolePanelTitle { + color: var(--community-roles-text-color); + font-size: var(--l-font-18); +} + +.rolePanelTitle:last-of-type { + margin-right: 32px; +} + +.separator { + border: 0; + margin: 0 32px 8px 32px; + width: 85%; + align-self: center; + height: 2px; + border: none; + border-top: var(--modal-separator) solid 1px; +} + +.rolePanelList { + display: flex; + flex-direction: column; + overflow-y: auto; + max-height: 400px; + margin: 0 32px 16px 24px; +} + +.createRoleButtonContainer { + width: 100%; + display: flex; + flex-direction: column; + align-self: center; + align-items: stretch; + margin-bottom: 16px; +} + +.createRoleButton { + margin: 0 32px 0 32px; +} diff --git a/web/roles/community-roles-modal.react.js b/web/roles/community-roles-modal.react.js new file mode 100644 index 000000000..7fb5d359f --- /dev/null +++ b/web/roles/community-roles-modal.react.js @@ -0,0 +1,82 @@ +// @flow + +import * as React from 'react'; + +import { useModalContext } from 'lib/components/modal-provider.react.js'; +import { threadInfoSelector } from 'lib/selectors/thread-selectors.js'; +import { useRoleMemberCountsForCommunity } from 'lib/shared/thread-utils.js'; +import type { ThreadInfo } from 'lib/types/thread-types.js'; + +import css from './community-roles-modal.css'; +import RolePanelEntry from './role-panel-entry.react.js'; +import Button, { buttonThemes } from '../components/button.react.js'; +import Modal from '../modals/modal.react.js'; +import { useSelector } from '../redux/redux-utils.js'; + +type CommunityRolesModalProps = { + +community: ThreadInfo, +}; + +function CommunityRolesModal(props: CommunityRolesModalProps): React.Node { + const { popModal } = useModalContext(); + const { community } = props; + + const [threadInfo, setThreadInfo] = React.useState(community); + const threadID = threadInfo.id; + const reduxThreadInfo: ?ThreadInfo = useSelector( + state => threadInfoSelector(state)[threadID], + ); + + React.useEffect(() => { + if (reduxThreadInfo) { + setThreadInfo(reduxThreadInfo); + } + }, [reduxThreadInfo]); + + const roleNamesToMembers = useRoleMemberCountsForCommunity(threadInfo); + + const rolePanelList = React.useMemo( + () => + Object.keys(roleNamesToMembers).map(roleName => ( + + )), + [roleNamesToMembers], + ); + + const onClickCreateRole = React.useCallback(() => {}, []); + + return ( + +
+ Roles help you group community members together and assign them certain + permissions. When people join the community, they are automatically + assigned the Members role. +
+
+ Communities must always have the Admins and Members role. +
+
+
Roles
+
Members
+
+
+
{rolePanelList}
+
+ +
+
+ ); +} + +export default CommunityRolesModal; diff --git a/web/roles/role-panel-entry.css b/web/roles/role-panel-entry.css new file mode 100644 index 000000000..9ec90fb78 --- /dev/null +++ b/web/roles/role-panel-entry.css @@ -0,0 +1,24 @@ +.rolePanelEntry { + display: flex; + flex-direction: row; + justify-content: space-between; + align-items: center; + padding: 12px; + color: var(--community-roles-text-color); + font-weight: 500; +} + +.rolePanelNameEntry { + font-size: var(--s-font-14); +} + +.rolePanelCountEntryContainer { + margin-right: 40px; + display: flex; + align-items: flex-end; +} + +.rolePanelCountEntry { + font-size: var(--s-font-14); + margin-right: 2px; +} diff --git a/web/roles/role-panel-entry.react.js b/web/roles/role-panel-entry.react.js new file mode 100644 index 000000000..64053c0de --- /dev/null +++ b/web/roles/role-panel-entry.react.js @@ -0,0 +1,26 @@ +// @flow + +import * as React from 'react'; + +import css from './role-panel-entry.css'; +import CommIcon from '../CommIcon.react.js'; + +type RolePanelEntryProps = { + +roleName: string, + +memberCount: number, +}; + +function RolePanelEntry(props: RolePanelEntryProps): React.Node { + const { roleName, memberCount } = props; + return ( +
+
{roleName}
+
+
{memberCount}
+ +
+
+ ); +} + +export default RolePanelEntry; diff --git a/web/theme.css b/web/theme.css index 731d88bcc..c89a254e3 100644 --- a/web/theme.css +++ b/web/theme.css @@ -1,242 +1,244 @@ :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; --logo-bg: #111827; --spoiler-color: #33332c; --loading-foreground: #1b0e38; --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-filled: var(--violet-dark-100); --btn-bg-outline: var(--shades-black-90); --btn-bg-success: var(--success-dark-50); --btn-bg-danger: var(--error-primary); --btn-bg-disabled: var(--shades-black-80); --btn-disabled-color: var(--shades-black-60); --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); --thread-hover-bg: var(--shades-black-80); --thread-active-bg: var(--shades-black-80); --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); --community-settings-selected: var(--violet-dark-60); --unread-bg: var(--error-primary); --settings-btn-bg: var(--violet-dark-100); --community-creation-btn-bg: var(--shades-black-80); --community-creation-ancestry-bg: var(--shades-black-80); --community-creation-ancestry-text: var(--shades-black-60); --community-creation-form-notice: var(--shades-white-60); --community-creation-keyserver-container: var(--shades-black-100); --modal-bg: var(--shades-black-90); --modal-fg: var(--shades-white-60); --join-bg: var(--shades-black-90); --help-color: var(--shades-black-60); --breadcrumb-color: var(--shades-white-60); --breadcrumb-color-unread: var(--shades-white-60); --btn-outline-border: var(--shades-black-60); --thread-color-read: var(--shades-black-60); --thread-preview-secondary: var(--shades-black-70); --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-search-button-color: var(--shades-black-60); --thread-ancestor-keyserver-border: var(--shades-black-70); --thread-ancestor-color: var(--shades-white-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); --message-action-tooltip-bg-light: var(--shades-black-80); --menu-bg: var(--shades-black-90); --menu-bg-light: var(--shades-black-80); --menu-separator-color: var(--shades-black-80); --menu-color: var(--shades-black-60); --menu-color-light: var(--shades-white-60); --menu-color-hover: var(--shades-white-100); --menu-color-dangerous: var(--error-primary); --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); --account-settings-label: var(--shades-black-60); --account-button-color: var(--violet-dark-100); --chat-thread-list-color-active: var(--shades-white-60); --chat-thread-list-menu-color: var(--shades-white-60); --chat-thread-list-menu-bg: var(--shades-black-80); --chat-thread-list-menu-active-color: var(--shades-white-60); --chat-thread-list-menu-active-bg: var(--shades-black-90); --search-clear-color: var(--shades-white-100); --search-clear-bg: var(--shades-black-70); --search-input-color: var(--shades-white-100); --search-input-placeholder: var(--shades-black-60); --search-icon-color: var(--shades-black-60); --tabs-header-active-color: var(--shades-white-100); --tabs-header-active-border: var(--violet-light-100); --tabs-header-active-background: var(--violet-dark-100); --tabs-header-background-color: var(--shades-black-60); --tabs-header-background-color-pill: var(--shades-white-60); --tabs-header-background-border: var(--shades-black-80); --tabs-header-background-color-hover: var(--shades-white-80); --tabs-header-background-border-hover: var(--shades-black-70); --members-modal-member-text: var(--shades-black-60); --members-modal-member-text-hover: var(--shades-white-100); --label-default-bg: var(--violet-dark-80); --label-default-color: var(--shades-white-80); --label-grey-bg: var(--shades-black-80); --label-grey-color: var(--shades-white-80); --subchannels-modal-color: var(--shades-black-60); --subchannels-modal-color-hover: var(--shades-white-100); --color-selector-active-bg: var(--shades-black-80); --relationship-modal-color: var(--shades-black-60); --arrow-extension-color: var(--shades-black-60); --modal-close-color: var(--shades-black-60); --modal-close-color-hover: var(--shades-white-100); --add-members-group-header-color: var(--shades-black-60); --add-members-item-color: var(--shades-black-60); --add-members-item-color-hover: var(--shades-white-100); --add-members-item-disabled-color: var(--shades-black-80); --add-members-item-disabled-color-hover: var(--shades-black-60); --add-members-remove-pending-color: var(--error-primary); --add-members-remove-pending-color-hover: var(--error-light-50); --radio-border: var(--shades-black-70); --radio-color: var(--shades-white-60); --notification-settings-option-selected-bg: var(--shades-black-80); --notification-settings-option-title-color: var(--shades-white-90); --notification-settings-option-color: var(--shades-white-60); --notification-settings-option-invalid-color: var(--shades-black-80); --notification-settings-option-invalid-selected-color: var(--shades-black-60); --danger-zone-subheading-color: var(--shades-white-60); --danger-zone-explanation-color: var(--shades-black-60); --thread-creation-search-container-bg: var(--shades-black-90); --thread-creation-close-search-color: var(--shades-black-60); --thread-creation-search-item-bg-hover: var(--shades-black-80); --thread-creation-search-item-info-color: var(--shades-black-60); --chat-message-list-active-border: #5989d6; --sidebars-modal-color: var(--shades-black-60); --sidebars-modal-color-hover: var(--shades-white-100); --inline-engagement-bg: var(--shades-black-70); --inline-engagement-bg-hover: var(--shades-black-80); --inline-engagement-color: var(--fg); --compose-subchannel-header-fg: var(--shades-black-60); --compose-subchannel-header-bg: var(--shades-black-80); --compose-subchannel-label-color: var(--shades-black-60); --compose-subchannel-mark-color: var(--violet-light-100); --enum-option-icon-color: var(--violet-dark-100); --show-password-bg-hover: var(--shades-black-70); --typeahead-overlay-light: var(--shades-black-80); --typeahead-overlay-dark: var(--shades-black-90); --typeahead-overlay-text: var(--shades-white-100); --typeahead-overlay-shadow-primary: rgba(0, 0, 0, 0.25); --typeahead-overlay-shadow-secondary: rgba(0, 0, 0, 0.4); --spoiler-text-color: var(--spoiler-color); --spoiler-background-color: var(--spoiler-color); --purple-link: var(--violet-light-100); --drawer-expand-button: var(--shades-black-60); --drawer-expand-button-disabled: var(--shades-black-80); --drawer-item-color: var(--shades-white-60); --drawer-active-item-color: var(--shades-white-100); --drawer-open-community-bg: #191919; --active-drawer-item-bg: rgba(0, 0, 0, 0.5); --community-drawer-lines: rgba(255, 255, 255, 0.08); --topbar-button-bg: var(--shades-black-90); --filters-button-bg: var(--shades-black-100); --filters-button-border: var(--shades-black-80); --filters-button-hover-bg: var(--shades-black-90); --filter-panel-fg: var(--shades-black-60); --filter-panel-bg: #0d0d0d; --topbar-button-bg-hover: var(--shades-black-80); --topbar-button-fg: var(--shades-white-60); --message-label-color: var(--shades-black-60); --topbar-lines: rgba(255, 255, 255, 0.08); --pin-message-information-text-color: var(--shades-white-60); --pin-message-modal-border-color: var(--shades-black-80); --pinned-count-banner-color: var(--shades-black-90); --pinned-count-text-color: var(--shades-white-90); --modal-overlay-background-90: rgba(0, 0, 0, 0.9); --modal-overlay-background-80: rgba(0, 0, 0, 0.8); --edit-avatar-button: var(--violet-dark-60); --dropdown-text: var(--shades-white-100); --dropdown-select-bg: var(--shades-black-90); --dropdown-select-border: var(--shades-white-60); --dropdown-option-bg: var(--shades-black-80); --dropdown-option-hover-bg: var(--shades-black-70); --dropdown-selected-option-check-color: var(--violet-dark-100); --dropdown-chevron-color: var(--shades-white-100); --dropdown-disabled-color: var(--shades-black-60); --change-member-role-modal-description-text: var(--shades-white-60); --change-member-role-modal-generic-text: var(--shades-white-100); --change-member-role-modal-disabled-background: var(--shades-black-80); --unsaved-changes-modal-text-color: var(--shades-white-60); --modal-secondary-label: var(--shades-black-60); + --community-roles-text-color: var(--shades-white-100); + --modal-separator: var(--shades-black-80); }