From 697791fc5de5f37d0c6ff6ec6afc62959dbe2491 Mon Sep 17 00:00:00 2001 From: Chewbacca Date: Thu, 30 Mar 2023 12:57:23 -0400 Subject: [PATCH 01/21] Add tests for GroupRelationship --- .../__tests__/group-relationship.test.tsx | 66 +++++++++++++++++++ .../group/components/group-relationship.tsx | 12 ++-- 2 files changed, 74 insertions(+), 4 deletions(-) create mode 100644 app/soapbox/features/group/components/__tests__/group-relationship.test.tsx diff --git a/app/soapbox/features/group/components/__tests__/group-relationship.test.tsx b/app/soapbox/features/group/components/__tests__/group-relationship.test.tsx new file mode 100644 index 0000000000..4c6c10a489 --- /dev/null +++ b/app/soapbox/features/group/components/__tests__/group-relationship.test.tsx @@ -0,0 +1,66 @@ +import React from 'react'; + +import { buildGroup, buildGroupRelationship } from 'soapbox/jest/factory'; +import { render, screen } from 'soapbox/jest/test-helpers'; +import { GroupRoles } from 'soapbox/schemas/group-member'; +import { Group } from 'soapbox/types/entities'; + +import GroupRelationship from '../group-relationship'; + +let group: Group; + +describe('', () => { + describe('when the user is an admin', () => { + beforeEach(() => { + group = buildGroup({ + relationship: buildGroupRelationship({ + requested: false, + member: true, + role: GroupRoles.ADMIN, + }), + }); + }); + + it('should render the relationship', () => { + render(); + + expect(screen.getByTestId('group-relationship')).toHaveTextContent('Admin'); + }); + }); + + describe('when the user is an owner', () => { + beforeEach(() => { + group = buildGroup({ + relationship: buildGroupRelationship({ + requested: false, + member: true, + role: GroupRoles.OWNER, + }), + }); + }); + + it('should render the relationship', () => { + render(); + + expect(screen.getByTestId('group-relationship')).toHaveTextContent('Owner'); + }); + }); + + describe('when the user is a member', () => { + beforeEach(() => { + group = buildGroup({ + relationship: buildGroupRelationship({ + requested: false, + member: true, + role: GroupRoles.USER, + }), + }); + }); + + it('should render null', () => { + render(); + + expect(screen.queryAllByTestId('group-relationship')).toHaveLength(0); + }); + }); +}); \ No newline at end of file diff --git a/app/soapbox/features/group/components/group-relationship.tsx b/app/soapbox/features/group/components/group-relationship.tsx index c71adbbe8b..771c0a3eeb 100644 --- a/app/soapbox/features/group/components/group-relationship.tsx +++ b/app/soapbox/features/group/components/group-relationship.tsx @@ -13,12 +13,16 @@ const GroupRelationship = ({ group }: IGroupRelationship) => { const isOwner = group.relationship?.role === GroupRoles.OWNER; const isAdmin = group.relationship?.role === GroupRoles.ADMIN; - if (!isOwner || !isAdmin) { + if (!isOwner && !isAdmin) { return null; } return ( - + { {isOwner - ? - : } + ? + : } ); From 4b3b6016591128b01b0ab1b089ffcb8d2a11d24b Mon Sep 17 00:00:00 2001 From: Chewbacca Date: Thu, 30 Mar 2023 12:57:42 -0400 Subject: [PATCH 02/21] Add tests for GroupOptionsButton --- .../__tests__/group-options-button.test.tsx | 85 +++++++++++++++++++ .../group/components/group-options-button.tsx | 11 ++- 2 files changed, 94 insertions(+), 2 deletions(-) create mode 100644 app/soapbox/features/group/components/__tests__/group-options-button.test.tsx diff --git a/app/soapbox/features/group/components/__tests__/group-options-button.test.tsx b/app/soapbox/features/group/components/__tests__/group-options-button.test.tsx new file mode 100644 index 0000000000..4d7779799a --- /dev/null +++ b/app/soapbox/features/group/components/__tests__/group-options-button.test.tsx @@ -0,0 +1,85 @@ +import React from 'react'; + +import { buildGroup, buildGroupRelationship } from 'soapbox/jest/factory'; +import { render, screen } from 'soapbox/jest/test-helpers'; +import { GroupRoles } from 'soapbox/schemas/group-member'; +import { Group } from 'soapbox/types/entities'; + +import GroupOptionsButton from '../group-options-button'; + +let group: Group; + +describe('', () => { + describe('when the user blocked', () => { + beforeEach(() => { + group = buildGroup({ + relationship: buildGroupRelationship({ + requested: false, + member: true, + blocked_by: true, + role: 'user', + }), + }); + }); + + it('should render null', () => { + render(); + + expect(screen.queryAllByTestId('dropdown-menu-button')).toHaveLength(0); + }); + }); + + describe('when the user is an admin', () => { + beforeEach(() => { + group = buildGroup({ + relationship: buildGroupRelationship({ + requested: false, + member: true, + role: GroupRoles.ADMIN, + }), + }); + }); + + it('should render null', () => { + render(); + + expect(screen.queryAllByTestId('dropdown-menu-button')).toHaveLength(0); + }); + }); + + describe('when the user is an owner', () => { + beforeEach(() => { + group = buildGroup({ + relationship: buildGroupRelationship({ + requested: false, + member: true, + role: GroupRoles.OWNER, + }), + }); + }); + + it('should render null', () => { + render(); + + expect(screen.queryAllByTestId('dropdown-menu-button')).toHaveLength(0); + }); + }); + + describe('when the user is a member', () => { + beforeEach(() => { + group = buildGroup({ + relationship: buildGroupRelationship({ + requested: false, + member: true, + role: GroupRoles.USER, + }), + }); + }); + + it('should render the dropdown menu', () => { + render(); + + expect(screen.queryAllByTestId('dropdown-menu-button')).toHaveLength(1); + }); + }); +}); \ No newline at end of file diff --git a/app/soapbox/features/group/components/group-options-button.tsx b/app/soapbox/features/group/components/group-options-button.tsx index 54ded91812..b6e83ae066 100644 --- a/app/soapbox/features/group/components/group-options-button.tsx +++ b/app/soapbox/features/group/components/group-options-button.tsx @@ -1,4 +1,5 @@ import React, { useMemo } from 'react'; +import { defineMessages, useIntl } from 'react-intl'; import { initReport, ReportableEntities } from 'soapbox/actions/reports'; import DropdownMenu, { Menu } from 'soapbox/components/dropdown-menu'; @@ -8,20 +9,25 @@ import { GroupRoles } from 'soapbox/schemas/group-member'; import type { Account, Group } from 'soapbox/types/entities'; +const messages = defineMessages({ + report: { id: 'group.report.label', defaultMessage: 'Report' }, +}); + interface IGroupActionButton { group: Group } const GroupOptionsButton = ({ group }: IGroupActionButton) => { - const dispatch = useAppDispatch(); const account = useOwnAccount(); + const dispatch = useAppDispatch(); + const intl = useIntl(); const isMember = group.relationship?.role === GroupRoles.USER; const isBlocked = group.relationship?.blocked_by; const menu: Menu = useMemo(() => ([ { - text: 'Report', + text: intl.formatMessage(messages.report), icon: require('@tabler/icons/flag.svg'), action: () => dispatch(initReport(ReportableEntities.GROUP, account as Account, { group })), }, @@ -38,6 +44,7 @@ const GroupOptionsButton = ({ group }: IGroupActionButton) => { theme='secondary' iconClassName='h-5 w-5' className='self-stretch px-2.5' + data-testid='dropdown-menu-button' /> ); From 681eacf82734d52982db3105cb35c10fba4b4d09 Mon Sep 17 00:00:00 2001 From: Chewbacca Date: Thu, 30 Mar 2023 15:19:14 -0400 Subject: [PATCH 03/21] Improve dark mode support of Groups + bug fixes --- .../components/groups/group-avatar.tsx | 3 +- .../components/ui/button/useButtonStyles.ts | 2 +- app/soapbox/components/ui/popover/popover.tsx | 52 ++++++++++--------- .../group/components/group-relationship.tsx | 1 + .../features/groups/pending-requests.tsx | 1 - .../components/placeholder-group-discover.tsx | 2 +- .../steps/confirmation-step.tsx | 44 ++++++++++------ 7 files changed, 60 insertions(+), 45 deletions(-) diff --git a/app/soapbox/components/groups/group-avatar.tsx b/app/soapbox/components/groups/group-avatar.tsx index 91d6808d24..9b3213bb9e 100644 --- a/app/soapbox/components/groups/group-avatar.tsx +++ b/app/soapbox/components/groups/group-avatar.tsx @@ -23,8 +23,9 @@ const GroupAvatar = (props: IGroupAvatar) => { className={ clsx('relative rounded-full', { 'shadow-[0_0_0_2px_theme(colors.primary.600),0_0_0_4px_theme(colors.white)]': isOwner && withRing, + 'dark:shadow-[0_0_0_2px_theme(colors.primary.600),0_0_0_4px_theme(colors.gray.800)]': isOwner && withRing, 'shadow-[0_0_0_2px_theme(colors.primary.600)]': isOwner && !withRing, - 'shadow-[0_0_0_2px_theme(colors.white)]': !isOwner && withRing, + 'shadow-[0_0_0_2px_theme(colors.white)] dark:shadow-[0_0_0_2px_theme(colors.gray.800)]': !isOwner && withRing, }) } src={group.avatar} diff --git a/app/soapbox/components/ui/button/useButtonStyles.ts b/app/soapbox/components/ui/button/useButtonStyles.ts index 1b61fc7b4b..740b06ba4d 100644 --- a/app/soapbox/components/ui/button/useButtonStyles.ts +++ b/app/soapbox/components/ui/button/useButtonStyles.ts @@ -9,7 +9,7 @@ const themes = { 'bg-transparent border-gray-400 dark:border-gray-800 hover:border-primary-300 dark:hover:border-primary-700 focus:border-primary-500 text-gray-900 dark:text-gray-100 focus:ring-primary-500', accent: 'border-transparent bg-secondary-500 hover:bg-secondary-400 focus:bg-secondary-500 text-gray-100 focus:ring-secondary-300', danger: 'border-transparent bg-danger-100 dark:bg-danger-900 text-danger-600 dark:text-danger-200 hover:bg-danger-600 hover:text-gray-100 dark:hover:text-gray-100 dark:hover:bg-danger-500 focus:ring-danger-500', - transparent: 'border-transparent text-gray-800 backdrop-blur-sm bg-white/75 hover:bg-white/80', + transparent: 'border-transparent bg-transparent text-primary-600 dark:text-accent-blue dark:bg-transparent hover:bg-gray-200 dark:hover:bg-gray-800/50', outline: 'border-gray-100 border-2 bg-transparent text-gray-100 hover:bg-white/10', muted: 'border border-solid bg-transparent border-gray-400 dark:border-gray-800 hover:border-primary-300 dark:hover:border-primary-700 focus:border-primary-500 text-gray-900 dark:text-gray-100 focus:ring-primary-500', }; diff --git a/app/soapbox/components/ui/popover/popover.tsx b/app/soapbox/components/ui/popover/popover.tsx index 7700f8149c..7f909f8bc6 100644 --- a/app/soapbox/components/ui/popover/popover.tsx +++ b/app/soapbox/components/ui/popover/popover.tsx @@ -13,6 +13,8 @@ import { import clsx from 'clsx'; import React, { useRef, useState } from 'react'; +import Portal from '../portal/portal'; + interface IPopover { children: React.ReactElement> /** The content of the popover */ @@ -83,31 +85,33 @@ const Popover: React.FC = (props) => { })} {(isMounted) && ( -
- {content} + +
+ {content} - -
+ +
+ )} ); diff --git a/app/soapbox/features/group/components/group-relationship.tsx b/app/soapbox/features/group/components/group-relationship.tsx index 771c0a3eeb..8fd47ac2d0 100644 --- a/app/soapbox/features/group/components/group-relationship.tsx +++ b/app/soapbox/features/group/components/group-relationship.tsx @@ -22,6 +22,7 @@ const GroupRelationship = ({ group }: IGroupRelationship) => { space={1} alignItems='center' data-testid='group-relationship' + className='text-primary-600 dark:text-accent-blue' > { const { groups, isLoading } = usePendingGroups(); - const renderBlankslate = () => ( { {/* Group Cover Image */}
- + {/* Group Avatar */}
diff --git a/app/soapbox/features/ui/components/modals/manage-group-modal/steps/confirmation-step.tsx b/app/soapbox/features/ui/components/modals/manage-group-modal/steps/confirmation-step.tsx index 1b71e36c91..7db6d9c209 100644 --- a/app/soapbox/features/ui/components/modals/manage-group-modal/steps/confirmation-step.tsx +++ b/app/soapbox/features/ui/components/modals/manage-group-modal/steps/confirmation-step.tsx @@ -3,8 +3,10 @@ import { FormattedMessage } from 'react-intl'; import { Avatar, Divider, HStack, Stack, Text, Button } from 'soapbox/components/ui'; +import type { Group } from 'soapbox/schemas'; + interface IConfirmationStep { - group: any + group: Group } const ConfirmationStep: React.FC = ({ group }) => { @@ -53,24 +55,30 @@ const ConfirmationStep: React.FC = ({ group }) => { - + + + - + + + - + + + @@ -96,7 +104,7 @@ interface IInfoListNumber { const InfoListNumber: React.FC = ({ number }) => { return ( -
+
{number}
); @@ -109,9 +117,11 @@ interface IInfoListItem { const InfoListItem: React.FC = ({ number, children }) => { return ( - -
-
{children}
+ + +
+ {children} +
); }; From a994d1c33eac8159452e942e261ce97bcc6a93dd Mon Sep 17 00:00:00 2001 From: Chewbacca Date: Thu, 30 Mar 2023 20:46:18 -0400 Subject: [PATCH 04/21] i18n --- app/soapbox/locales/en.json | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/app/soapbox/locales/en.json b/app/soapbox/locales/en.json index 1bc6cba4cc..81a4ed1dab 100644 --- a/app/soapbox/locales/en.json +++ b/app/soapbox/locales/en.json @@ -799,8 +799,9 @@ "group.promote.admin.confirmation.message": "Are you sure you want to assign the admin role to @{name}?", "group.promote.admin.confirmation.title": "Assign Admin Role", "group.promote.admin.success": "@{name} is now an admin", + "group.report.label": "Report", "group.role.admin": "Admin", - "group.role.moderator": "Moderator", + "group.role.owner": "Owner", "group.tabs.all": "All", "group.tabs.members": "Members", "group.upload_banner": "Upload photo", From 5720d396fce21b7698e3dd884346f2d0e3c674f5 Mon Sep 17 00:00:00 2001 From: Alex Gleason Date: Thu, 30 Mar 2023 20:41:44 -0500 Subject: [PATCH 05/21] Groups: make "share with followers" button work --- app/soapbox/actions/compose.ts | 14 ++++++++- .../compose/components/compose-form.tsx | 5 +++- app/soapbox/features/group/group-timeline.tsx | 30 ++++++++++++++----- app/soapbox/reducers/compose.ts | 4 +++ 4 files changed, 44 insertions(+), 9 deletions(-) diff --git a/app/soapbox/actions/compose.ts b/app/soapbox/actions/compose.ts index 8be60bfc75..995f37c151 100644 --- a/app/soapbox/actions/compose.ts +++ b/app/soapbox/actions/compose.ts @@ -48,6 +48,7 @@ const COMPOSE_UPLOAD_FAIL = 'COMPOSE_UPLOAD_FAIL'; const COMPOSE_UPLOAD_PROGRESS = 'COMPOSE_UPLOAD_PROGRESS'; const COMPOSE_UPLOAD_UNDO = 'COMPOSE_UPLOAD_UNDO'; const COMPOSE_GROUP_POST = 'COMPOSE_GROUP_POST'; +const COMPOSE_SET_GROUP_TIMELINE_VISIBLE = 'COMPOSE_SET_GROUP_TIMELINE_VISIBLE'; const COMPOSE_SUGGESTIONS_CLEAR = 'COMPOSE_SUGGESTIONS_CLEAR'; const COMPOSE_SUGGESTIONS_READY = 'COMPOSE_SUGGESTIONS_READY'; @@ -292,7 +293,10 @@ const submitCompose = (composeId: string, routerHistory?: History, force = false to, }; - if (compose.privacy === 'group') params.group_id = compose.group_id; + if (compose.privacy === 'group') { + params.group_id = compose.group_id; + params.group_timeline_visible = compose.group_timeline_visible; // Truth Social + } dispatch(createStatus(params, idempotencyKey, statusId)).then(function(data) { if (!statusId && data.visibility === 'direct' && getState().conversations.mounted <= 0 && routerHistory) { @@ -483,6 +487,12 @@ const groupCompose = (composeId: string, groupId: string) => }); }; +const setGroupTimelineVisible = (composeId: string, groupTimelineVisible: boolean) => ({ + type: COMPOSE_SET_GROUP_TIMELINE_VISIBLE, + id: composeId, + groupTimelineVisible, +}); + const clearComposeSuggestions = (composeId: string) => { if (cancelFetchComposeSuggestionsAccounts) { cancelFetchComposeSuggestionsAccounts(); @@ -792,6 +802,7 @@ export { COMPOSE_ADD_TO_MENTIONS, COMPOSE_REMOVE_FROM_MENTIONS, COMPOSE_SET_STATUS, + COMPOSE_SET_GROUP_TIMELINE_VISIBLE, setComposeToStatus, changeCompose, replyCompose, @@ -818,6 +829,7 @@ export { uploadComposeFail, undoUploadCompose, groupCompose, + setGroupTimelineVisible, clearComposeSuggestions, fetchComposeSuggestions, readyComposeSuggestionsEmojis, diff --git a/app/soapbox/features/compose/components/compose-form.tsx b/app/soapbox/features/compose/components/compose-form.tsx index 00167886f1..321b30aa2e 100644 --- a/app/soapbox/features/compose/components/compose-form.tsx +++ b/app/soapbox/features/compose/components/compose-form.tsx @@ -63,9 +63,10 @@ interface IComposeForm { clickableAreaRef?: React.RefObject event?: string group?: string + extra?: React.ReactNode } -const ComposeForm = ({ id, shouldCondense, autoFocus, clickableAreaRef, event, group }: IComposeForm) => { +const ComposeForm = ({ id, shouldCondense, autoFocus, clickableAreaRef, event, group, extra }: IComposeForm) => { const history = useHistory(); const intl = useIntl(); const dispatch = useAppDispatch(); @@ -333,6 +334,8 @@ const ComposeForm = ({ id, shouldCondense, autoFocus, clickab + {extra &&
{extra}
} +
= (props) => { const { group } = useGroup(groupId); + const composeId = `group:${groupId}`; const canComposeGroupStatus = !!account && group?.relationship?.member; + const groupTimelineVisible = useAppSelector((state) => !!state.compose.get(composeId)?.group_timeline_visible); const handleLoadMore = (maxId: string) => { dispatch(expandGroupTimeline(groupId, { maxId })); }; + const handleToggleChange = () => { + dispatch(setGroupTimelineVisible(composeId, !groupTimelineVisible)); + }; + useEffect(() => { dispatch(expandGroupTimeline(groupId)); - - dispatch(groupCompose(`group:${groupId}`, groupId)); + dispatch(groupCompose(composeId, groupId)); const disconnect = dispatch(connectGroupStream(groupId)); @@ -58,10 +63,21 @@ const GroupTimeline: React.FC = (props) => { +
+ Share with my followers +
+ + + )} />
@@ -69,7 +85,7 @@ const GroupTimeline: React.FC = (props) => { diff --git a/app/soapbox/reducers/compose.ts b/app/soapbox/reducers/compose.ts index 63bec8be6b..cfce14a5ad 100644 --- a/app/soapbox/reducers/compose.ts +++ b/app/soapbox/reducers/compose.ts @@ -51,6 +51,7 @@ import { COMPOSE_REMOVE_FROM_MENTIONS, COMPOSE_SET_STATUS, COMPOSE_EVENT_REPLY, + COMPOSE_SET_GROUP_TIMELINE_VISIBLE, } from '../actions/compose'; import { ME_FETCH_SUCCESS, ME_PATCH_SUCCESS } from '../actions/me'; import { SETTING_CHANGE, FE_NAME } from '../actions/settings'; @@ -103,6 +104,7 @@ export const ReducerCompose = ImmutableRecord({ tagHistory: ImmutableList(), text: '', to: ImmutableOrderedSet(), + group_timeline_visible: false, // TruthSocial }); type State = ImmutableMap; @@ -493,6 +495,8 @@ export default function compose(state = initialState, action: AnyAction) { return updateCompose(state, action.id, compose => compose.update('to', mentions => mentions!.add(action.account))); case COMPOSE_REMOVE_FROM_MENTIONS: return updateCompose(state, action.id, compose => compose.update('to', mentions => mentions!.delete(action.account))); + case COMPOSE_SET_GROUP_TIMELINE_VISIBLE: + return updateCompose(state, action.id, compose => compose.set('group_timeline_visible', action.groupTimelineVisible)); case ME_FETCH_SUCCESS: return updateCompose(state, 'default', compose => importAccount(compose, action.me)); case ME_PATCH_SUCCESS: From eeafb3073ee8d16c4daffd5b04a71bc0df2a8ebc Mon Sep 17 00:00:00 2001 From: Chewbacca Date: Fri, 31 Mar 2023 08:00:21 -0400 Subject: [PATCH 06/21] Change discover chunk to groups --- app/soapbox/features/ui/util/async-components.ts | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/app/soapbox/features/ui/util/async-components.ts b/app/soapbox/features/ui/util/async-components.ts index 0177767922..381f033ad3 100644 --- a/app/soapbox/features/ui/util/async-components.ts +++ b/app/soapbox/features/ui/util/async-components.ts @@ -551,19 +551,19 @@ export function Groups() { } export function GroupsDiscover() { - return import(/* webpackChunkName: "features/groups/discover" */'../../groups/discover'); + return import(/* webpackChunkName: "features/groups" */'../../groups/discover'); } export function GroupsPopular() { - return import(/* webpackChunkName: "features/groups/discover" */'../../groups/popular'); + return import(/* webpackChunkName: "features/groups" */'../../groups/popular'); } export function GroupsSuggested() { - return import(/* webpackChunkName: "features/groups/discover" */'../../groups/suggested'); + return import(/* webpackChunkName: "features/groups" */'../../groups/suggested'); } export function PendingGroupRequests() { - return import(/* webpackChunkName: "features/groups/discover" */'../../groups/pending-requests'); + return import(/* webpackChunkName: "features/groups" */'../../groups/pending-requests'); } export function GroupMembers() { From 541d48c5abb5d98a3fc5b9674792ab2707cc8d41 Mon Sep 17 00:00:00 2001 From: Alex Gleason Date: Fri, 31 Mar 2023 11:06:43 -0500 Subject: [PATCH 07/21] Replace Toggle with a custom component --- app/soapbox/components/ui/toggle/toggle.tsx | 38 +++++++++++++++---- app/soapbox/features/filters/edit-filter.tsx | 7 ---- .../modals/edit-announcement-modal.tsx | 1 - 3 files changed, 31 insertions(+), 15 deletions(-) diff --git a/app/soapbox/components/ui/toggle/toggle.tsx b/app/soapbox/components/ui/toggle/toggle.tsx index 466a207f81..27334b1df4 100644 --- a/app/soapbox/components/ui/toggle/toggle.tsx +++ b/app/soapbox/components/ui/toggle/toggle.tsx @@ -1,13 +1,37 @@ -import React from 'react'; -import ReactToggle, { ToggleProps } from 'react-toggle'; +import clsx from 'clsx'; +import React, { useRef } from 'react'; + +interface IToggle extends Pick, 'id' | 'checked' | 'onChange' | 'required'> { + +} /** A glorified checkbox. Wrapper around react-toggle. */ -const Toggle: React.FC = ({ icons = false, ...rest }) => { +const Toggle: React.FC = ({ id, checked, onChange, required }) => { + const input = useRef(null); + + const handleClick: React.MouseEventHandler = (e) => { + e.preventDefault(); + input.current?.focus(); + input.current?.click(); + }; + return ( - + ); }; diff --git a/app/soapbox/features/filters/edit-filter.tsx b/app/soapbox/features/filters/edit-filter.tsx index 4d035f8d32..9fd5363fa4 100644 --- a/app/soapbox/features/filters/edit-filter.tsx +++ b/app/soapbox/features/filters/edit-filter.tsx @@ -71,7 +71,6 @@ const FilterField: StreamfieldComponent = ({ value, onChange }) => @@ -212,28 +211,24 @@ const EditFilter: React.FC = ({ params }) => { setHomeTimeline(target.checked)} /> setPublicTimeline(target.checked)} /> setNotifications(target.checked)} /> setConversations(target.checked)} /> @@ -241,7 +236,6 @@ const EditFilter: React.FC = ({ params }) => { {features.filtersV2 && ( setAccounts(target.checked)} /> @@ -255,7 +249,6 @@ const EditFilter: React.FC = ({ params }) => { hint={intl.formatMessage(features.filtersV2 ? messages.hide_hint : messages.drop_hint)} > setHide(target.checked)} /> diff --git a/app/soapbox/features/ui/components/modals/edit-announcement-modal.tsx b/app/soapbox/features/ui/components/modals/edit-announcement-modal.tsx index 1835198179..3b8c7736f0 100644 --- a/app/soapbox/features/ui/components/modals/edit-announcement-modal.tsx +++ b/app/soapbox/features/ui/components/modals/edit-announcement-modal.tsx @@ -98,7 +98,6 @@ const EditAnnouncementModal: React.FC = ({ onClose }) => From e38b5fbfd36d1c36b23f6f5fcae797854d51b09d Mon Sep 17 00:00:00 2001 From: Alex Gleason Date: Fri, 31 Mar 2023 11:15:48 -0500 Subject: [PATCH 08/21] Toggle: add size prop --- app/soapbox/components/ui/toggle/toggle.tsx | 17 +++++++++++++---- 1 file changed, 13 insertions(+), 4 deletions(-) diff --git a/app/soapbox/components/ui/toggle/toggle.tsx b/app/soapbox/components/ui/toggle/toggle.tsx index 27334b1df4..822dae28d7 100644 --- a/app/soapbox/components/ui/toggle/toggle.tsx +++ b/app/soapbox/components/ui/toggle/toggle.tsx @@ -2,11 +2,11 @@ import clsx from 'clsx'; import React, { useRef } from 'react'; interface IToggle extends Pick, 'id' | 'checked' | 'onChange' | 'required'> { - + size?: 'sm' | 'md' } /** A glorified checkbox. Wrapper around react-toggle. */ -const Toggle: React.FC = ({ id, checked, onChange, required }) => { +const Toggle: React.FC = ({ id, size = 'md', checked, onChange, required }) => { const input = useRef(null); const handleClick: React.MouseEventHandler = (e) => { @@ -17,10 +17,19 @@ const Toggle: React.FC = ({ id, checked, onChange, required }) => { return ( ); From d7cb52c40c4727ded4625da0d03b7beadc10923a Mon Sep 17 00:00:00 2001 From: Alex Gleason Date: Fri, 31 Mar 2023 11:50:32 -0500 Subject: [PATCH 12/21] Toggle: add "name" prop --- app/soapbox/components/ui/toggle/toggle.tsx | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/app/soapbox/components/ui/toggle/toggle.tsx b/app/soapbox/components/ui/toggle/toggle.tsx index 545c2bd993..e71f34eb8c 100644 --- a/app/soapbox/components/ui/toggle/toggle.tsx +++ b/app/soapbox/components/ui/toggle/toggle.tsx @@ -1,12 +1,12 @@ import clsx from 'clsx'; import React, { useRef } from 'react'; -interface IToggle extends Pick, 'id' | 'checked' | 'onChange' | 'required' | 'disabled'> { +interface IToggle extends Pick, 'id' | 'name' | 'checked' | 'onChange' | 'required' | 'disabled'> { size?: 'sm' | 'md' } /** A glorified checkbox. */ -const Toggle: React.FC = ({ id, size = 'md', checked, onChange, required, disabled }) => { +const Toggle: React.FC = ({ id, size = 'md', name, checked, onChange, required, disabled }) => { const input = useRef(null); const handleClick: React.MouseEventHandler = (e) => { @@ -39,6 +39,7 @@ const Toggle: React.FC = ({ id, size = 'md', checked, onChange, require Date: Fri, 31 Mar 2023 11:52:19 -0500 Subject: [PATCH 13/21] Remove react-toggle --- .../report/components/status-check-box.tsx | 4 +- .../compose-event-modal.tsx | 5 +- .../modals/edit-federation-modal.tsx | 8 +- .../ui/components/modals/mute-modal.tsx | 5 +- .../report-modal/steps/other-actions-step.tsx | 5 +- app/styles/application.scss | 1 - app/styles/components/react-toggle.scss | 96 ------------------- package.json | 2 - yarn.lock | 14 --- 9 files changed, 6 insertions(+), 134 deletions(-) delete mode 100644 app/styles/components/react-toggle.scss diff --git a/app/soapbox/features/report/components/status-check-box.tsx b/app/soapbox/features/report/components/status-check-box.tsx index 9bb20dc774..78e3cbe793 100644 --- a/app/soapbox/features/report/components/status-check-box.tsx +++ b/app/soapbox/features/report/components/status-check-box.tsx @@ -1,9 +1,9 @@ import noop from 'lodash/noop'; import React from 'react'; -import Toggle from 'react-toggle'; import { toggleStatusReport } from 'soapbox/actions/reports'; import StatusContent from 'soapbox/components/status-content'; +import { Toggle } from 'soapbox/components/ui'; import { useAppDispatch, useAppSelector } from 'soapbox/hooks'; import Bundle from '../../ui/components/bundle'; @@ -88,7 +88,7 @@ const StatusCheckBox: React.FC = ({ id, disabled }) => {
- +
); diff --git a/app/soapbox/features/ui/components/modals/compose-event-modal/compose-event-modal.tsx b/app/soapbox/features/ui/components/modals/compose-event-modal/compose-event-modal.tsx index 5aa862c285..bb5d7659ee 100644 --- a/app/soapbox/features/ui/components/modals/compose-event-modal/compose-event-modal.tsx +++ b/app/soapbox/features/ui/components/modals/compose-event-modal/compose-event-modal.tsx @@ -1,6 +1,5 @@ import React, { useEffect, useState } from 'react'; import { defineMessages, FormattedMessage, useIntl } from 'react-intl'; -import Toggle from 'react-toggle'; import { changeEditEventApprovalRequired, @@ -22,7 +21,7 @@ import { closeModal, openModal } from 'soapbox/actions/modals'; import { ADDRESS_ICONS } from 'soapbox/components/autosuggest-location'; import LocationSearch from 'soapbox/components/location-search'; import { checkEventComposeContent } from 'soapbox/components/modal-root'; -import { Button, Form, FormGroup, HStack, Icon, IconButton, Input, Modal, Spinner, Stack, Tabs, Text, Textarea } from 'soapbox/components/ui'; +import { Button, Form, FormGroup, HStack, Icon, IconButton, Input, Modal, Spinner, Stack, Tabs, Text, Textarea, Toggle } from 'soapbox/components/ui'; import AccountContainer from 'soapbox/containers/account-container'; import { isCurrentOrFutureDate } from 'soapbox/features/compose/components/schedule-form'; import BundleContainer from 'soapbox/features/ui/containers/bundle-container'; @@ -273,7 +272,6 @@ const ComposeEventModal: React.FC = ({ onClose }) => { @@ -302,7 +300,6 @@ const ComposeEventModal: React.FC = ({ onClose }) => { {!id && ( diff --git a/app/soapbox/features/ui/components/modals/edit-federation-modal.tsx b/app/soapbox/features/ui/components/modals/edit-federation-modal.tsx index 59930e380c..a8851420f5 100644 --- a/app/soapbox/features/ui/components/modals/edit-federation-modal.tsx +++ b/app/soapbox/features/ui/components/modals/edit-federation-modal.tsx @@ -1,11 +1,10 @@ import { Map as ImmutableMap } from 'immutable'; import React, { useState, useEffect, useCallback } from 'react'; import { defineMessages, FormattedMessage, useIntl } from 'react-intl'; -import Toggle from 'react-toggle'; import { updateMrf } from 'soapbox/actions/mrf'; import List, { ListItem } from 'soapbox/components/list'; -import { Modal } from 'soapbox/components/ui'; +import { Modal, Toggle } from 'soapbox/components/ui'; import { useAppSelector, useAppDispatch } from 'soapbox/hooks'; import { makeGetRemoteInstance } from 'soapbox/selectors'; import toast from 'soapbox/toast'; @@ -86,7 +85,6 @@ const EditFederationModal: React.FC = ({ host, onClose }) @@ -95,7 +93,6 @@ const EditFederationModal: React.FC = ({ host, onClose }) @@ -105,7 +102,6 @@ const EditFederationModal: React.FC = ({ host, onClose }) @@ -115,7 +111,6 @@ const EditFederationModal: React.FC = ({ host, onClose }) @@ -125,7 +120,6 @@ const EditFederationModal: React.FC = ({ host, onClose }) diff --git a/app/soapbox/features/ui/components/modals/mute-modal.tsx b/app/soapbox/features/ui/components/modals/mute-modal.tsx index 44ba725fed..5c8dd603ed 100644 --- a/app/soapbox/features/ui/components/modals/mute-modal.tsx +++ b/app/soapbox/features/ui/components/modals/mute-modal.tsx @@ -1,11 +1,10 @@ import React from 'react'; import { FormattedMessage } from 'react-intl'; -import Toggle from 'react-toggle'; import { muteAccount } from 'soapbox/actions/accounts'; import { closeModal } from 'soapbox/actions/modals'; import { toggleHideNotifications, changeMuteDuration } from 'soapbox/actions/mutes'; -import { Modal, HStack, Stack, Text } from 'soapbox/components/ui'; +import { Modal, HStack, Stack, Text, Toggle } from 'soapbox/components/ui'; import DurationSelector from 'soapbox/features/compose/components/polls/duration-selector'; import { useAppDispatch, useAppSelector, useFeatures } from 'soapbox/hooks'; import { makeGetAccount } from 'soapbox/selectors'; @@ -74,7 +73,6 @@ const MuteModal = () => { @@ -90,7 +88,6 @@ const MuteModal = () => { diff --git a/app/soapbox/features/ui/components/modals/report-modal/steps/other-actions-step.tsx b/app/soapbox/features/ui/components/modals/report-modal/steps/other-actions-step.tsx index 8f58c4b35f..55e4356039 100644 --- a/app/soapbox/features/ui/components/modals/report-modal/steps/other-actions-step.tsx +++ b/app/soapbox/features/ui/components/modals/report-modal/steps/other-actions-step.tsx @@ -1,11 +1,10 @@ import { OrderedSet } from 'immutable'; import React, { useEffect, useState } from 'react'; import { defineMessages, FormattedMessage, useIntl } from 'react-intl'; -import Toggle from 'react-toggle'; import { changeReportBlock, changeReportForward } from 'soapbox/actions/reports'; import { fetchRules } from 'soapbox/actions/rules'; -import { Button, FormGroup, HStack, Stack, Text } from 'soapbox/components/ui'; +import { Button, FormGroup, HStack, Stack, Text, Toggle } from 'soapbox/components/ui'; import StatusCheckBox from 'soapbox/features/report/components/status-check-box'; import { useAppDispatch, useAppSelector, useFeatures } from 'soapbox/hooks'; import { isRemote, getDomain } from 'soapbox/utils/accounts'; @@ -101,7 +100,6 @@ const OtherActionsStep = ({ account }: IOtherActionsStep) => { @@ -119,7 +117,6 @@ const OtherActionsStep = ({ account }: IOtherActionsStep) => { diff --git a/app/styles/application.scss b/app/styles/application.scss index d54785550f..f0a17c975f 100644 --- a/app/styles/application.scss +++ b/app/styles/application.scss @@ -21,7 +21,6 @@ @import 'components/display-name'; @import 'components/columns'; @import 'components/search'; -@import 'components/react-toggle'; @import 'components/video-player'; @import 'components/audio-player'; @import 'components/crypto-donate'; diff --git a/app/styles/components/react-toggle.scss b/app/styles/components/react-toggle.scss deleted file mode 100644 index a56db522a0..0000000000 --- a/app/styles/components/react-toggle.scss +++ /dev/null @@ -1,96 +0,0 @@ -.react-toggle { - display: inline-block; - position: relative; - cursor: pointer; - background-color: transparent; - border: 0; - padding: 0; - user-select: none; - -webkit-tap-highlight-color: #0000; - -webkit-tap-highlight-color: transparent; - - &:focus-within .react-toggle-track { - @apply ring-2 ring-offset-2 ring-primary-500; - } -} - -.react-toggle-screenreader-only { - border: 0; - clip: rect(0 0 0 0); - height: 1px; - margin: -1px; - overflow: hidden; - padding: 0; - position: absolute; - width: 1px; -} - -.react-toggle--disabled { - cursor: not-allowed; - opacity: 0.5; - transition: opacity 0.25s; -} - -.react-toggle-track { - @apply bg-gray-500 dark:bg-gray-700 h-[30px] w-[50px] p-0 rounded-full transition-colors; -} - -.react-toggle--checked .react-toggle-track { - @apply bg-primary-600 dark:bg-accent-blue; -} - -.react-toggle-track-check { - position: absolute; - width: 14px; - height: 10px; - top: 0; - bottom: 0; - margin-top: auto; - margin-bottom: auto; - line-height: 0; - left: 8px; - opacity: 0; - transition: opacity 0.25s ease; -} - -.react-toggle--checked .react-toggle-track-check { - opacity: 1; - transition: opacity 0.25s ease; -} - -.react-toggle-track-x { - position: absolute; - width: 10px; - height: 10px; - top: 0; - bottom: 0; - margin-top: auto; - margin-bottom: auto; - line-height: 0; - right: 10px; - opacity: 1; - transition: opacity 0.25s ease; -} - -.react-toggle--checked .react-toggle-track-x { - opacity: 0; -} - -.react-toggle-thumb { - position: absolute; - top: 1px; - left: 1px; - width: 28px; - height: 28px; - border: 1px solid #fff; - border-radius: 50%; - background-color: #fff; - box-sizing: border-box; - transition: all 0.25s ease; - transition-property: border-color, left; -} - -.react-toggle--checked .react-toggle-thumb { - @apply border-primary-600 dark:border-accent-blue; - left: 21px; -} diff --git a/package.json b/package.json index 079f2173fe..f1d6bf54ed 100644 --- a/package.json +++ b/package.json @@ -87,7 +87,6 @@ "@types/react-router-dom": "^5.3.3", "@types/react-sparklines": "^1.7.2", "@types/react-swipeable-views": "^0.13.1", - "@types/react-toggle": "^4.0.3", "@types/redux-mock-store": "^1.0.3", "@types/seedrandom": "^3.0.2", "@types/semver": "^7.3.9", @@ -165,7 +164,6 @@ "react-sticky-box": "^2.0.0", "react-swipeable-views": "^0.14.0", "react-textarea-autosize": "^8.3.4", - "react-toggle": "^4.1.2", "react-virtuoso": "^4.0.8", "redux": "^4.1.1", "redux-immutable": "^4.0.0", diff --git a/yarn.lock b/yarn.lock index 12eb37b1cc..4b51ca0555 100644 --- a/yarn.lock +++ b/yarn.lock @@ -4497,13 +4497,6 @@ dependencies: "@types/react" "*" -"@types/react-toggle@^4.0.3": - version "4.0.3" - resolved "https://registry.yarnpkg.com/@types/react-toggle/-/react-toggle-4.0.3.tgz#8db98ac8d2c5e8c03c2d3a42027555c1cd2289da" - integrity sha512-57QdMWeeQdRjM2/p+udgYerxUbSkmeUIW18kwUttcci6GHkgxoqCsDZfRtsCsAHcvvM5VBQdtDUEgLWo2e87mA== - dependencies: - "@types/react" "*" - "@types/react@*", "@types/react@17", "@types/react@^18.0.26": version "18.0.26" resolved "https://registry.yarnpkg.com/@types/react/-/react-18.0.26.tgz#8ad59fc01fef8eaf5c74f4ea392621749f0b7917" @@ -14819,13 +14812,6 @@ react-textarea-autosize@^8.3.4: use-composed-ref "^1.3.0" use-latest "^1.2.1" -react-toggle@^4.1.2: - version "4.1.2" - resolved "https://registry.yarnpkg.com/react-toggle/-/react-toggle-4.1.2.tgz#b00500832f925ad524356d909821821ae39f6c52" - integrity sha512-4Ohw31TuYQdhWfA6qlKafeXx3IOH7t4ZHhmRdwsm1fQREwOBGxJT+I22sgHqR/w8JRdk+AeMCJXPImEFSrNXow== - dependencies: - classnames "^2.2.5" - react-transition-group@^2.2.1: version "2.9.0" resolved "https://registry.yarnpkg.com/react-transition-group/-/react-transition-group-2.9.0.tgz#df9cdb025796211151a436c69a8f3b97b5b07c8d" From 2b5a3f720d88bc42019274b25deb46b2d7ad3b8b Mon Sep 17 00:00:00 2001 From: Alex Gleason Date: Fri, 31 Mar 2023 11:58:10 -0500 Subject: [PATCH 14/21] Toggle: fix size sm track --- app/soapbox/components/ui/toggle/toggle.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/soapbox/components/ui/toggle/toggle.tsx b/app/soapbox/components/ui/toggle/toggle.tsx index e71f34eb8c..a925019847 100644 --- a/app/soapbox/components/ui/toggle/toggle.tsx +++ b/app/soapbox/components/ui/toggle/toggle.tsx @@ -30,7 +30,7 @@ const Toggle: React.FC = ({ id, size = 'md', name, checked, onChange, r >
Date: Fri, 31 Mar 2023 11:58:23 -0500 Subject: [PATCH 15/21] GroupTimeline: use size sm Toggle --- app/soapbox/features/group/group-timeline.tsx | 1 + 1 file changed, 1 insertion(+) diff --git a/app/soapbox/features/group/group-timeline.tsx b/app/soapbox/features/group/group-timeline.tsx index c84234e969..0a69af66ba 100644 --- a/app/soapbox/features/group/group-timeline.tsx +++ b/app/soapbox/features/group/group-timeline.tsx @@ -75,6 +75,7 @@ const GroupTimeline: React.FC = (props) => { )} From 406e6f3f404f8825f68ee7471e34e3b77857ed84 Mon Sep 17 00:00:00 2001 From: Alex Gleason Date: Fri, 31 Mar 2023 12:00:15 -0500 Subject: [PATCH 16/21] GroupTimeline: make label clickable --- app/soapbox/features/group/group-timeline.tsx | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/app/soapbox/features/group/group-timeline.tsx b/app/soapbox/features/group/group-timeline.tsx index 0a69af66ba..de399697af 100644 --- a/app/soapbox/features/group/group-timeline.tsx +++ b/app/soapbox/features/group/group-timeline.tsx @@ -69,10 +69,11 @@ const GroupTimeline: React.FC = (props) => { group={groupId} extra={( -
+
+ Date: Fri, 31 Mar 2023 12:04:13 -0500 Subject: [PATCH 17/21] Toggle: prevent clicking label getting it stuck --- app/soapbox/components/ui/toggle/toggle.tsx | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/app/soapbox/components/ui/toggle/toggle.tsx b/app/soapbox/components/ui/toggle/toggle.tsx index a925019847..34651e98d2 100644 --- a/app/soapbox/components/ui/toggle/toggle.tsx +++ b/app/soapbox/components/ui/toggle/toggle.tsx @@ -9,8 +9,7 @@ interface IToggle extends Pick, 'id' const Toggle: React.FC = ({ id, size = 'md', name, checked, onChange, required, disabled }) => { const input = useRef(null); - const handleClick: React.MouseEventHandler = (e) => { - e.preventDefault(); + const handleClick: React.MouseEventHandler = () => { input.current?.focus(); input.current?.click(); }; From 59860b405afae7d2e3b2ca932d18a2cd0adca1b0 Mon Sep 17 00:00:00 2001 From: Alex Gleason Date: Fri, 31 Mar 2023 12:17:20 -0500 Subject: [PATCH 18/21] GroupTimeline: use FormattedMessage --- app/soapbox/features/group/group-timeline.tsx | 4 +++- app/soapbox/locales/en.json | 1 + 2 files changed, 4 insertions(+), 1 deletion(-) diff --git a/app/soapbox/features/group/group-timeline.tsx b/app/soapbox/features/group/group-timeline.tsx index de399697af..6162b2ad00 100644 --- a/app/soapbox/features/group/group-timeline.tsx +++ b/app/soapbox/features/group/group-timeline.tsx @@ -70,7 +70,9 @@ const GroupTimeline: React.FC = (props) => { extra={( Date: Fri, 31 Mar 2023 13:46:09 -0500 Subject: [PATCH 19/21] Only show toggle for public groups --- app/soapbox/features/group/group-timeline.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/soapbox/features/group/group-timeline.tsx b/app/soapbox/features/group/group-timeline.tsx index 6162b2ad00..f718b9c87d 100644 --- a/app/soapbox/features/group/group-timeline.tsx +++ b/app/soapbox/features/group/group-timeline.tsx @@ -67,7 +67,7 @@ const GroupTimeline: React.FC = (props) => { shouldCondense autoFocus={false} group={groupId} - extra={( + extra={!group.locked && (