From d69d7c0507f4e0453e032c897cb3343065798381 Mon Sep 17 00:00:00 2001 From: Echo Date: Fri, 27 Feb 2026 14:36:19 +0100 Subject: [PATCH] Profile editing: Tab display controls (#37994) --- .../components/profile_display_modal.tsx | 122 ++++++++++++++++++ .../mastodon/features/account_edit/index.tsx | 19 ++- .../features/account_edit/styles.module.scss | 10 ++ .../features/ui/components/dialog_modal.tsx | 94 ++++++++++++++ .../features/ui/components/modal_root.jsx | 1 + app/javascript/mastodon/locales/en.json | 9 ++ 6 files changed, 253 insertions(+), 2 deletions(-) create mode 100644 app/javascript/mastodon/features/account_edit/components/profile_display_modal.tsx create mode 100644 app/javascript/mastodon/features/ui/components/dialog_modal.tsx diff --git a/app/javascript/mastodon/features/account_edit/components/profile_display_modal.tsx b/app/javascript/mastodon/features/account_edit/components/profile_display_modal.tsx new file mode 100644 index 0000000000..edf98e977c --- /dev/null +++ b/app/javascript/mastodon/features/account_edit/components/profile_display_modal.tsx @@ -0,0 +1,122 @@ +import type { ChangeEventHandler, FC } from 'react'; +import { useCallback } from 'react'; + +import { FormattedMessage, useIntl } from 'react-intl'; + +import { Callout } from '@/mastodon/components/callout'; +import { ToggleField } from '@/mastodon/components/form_fields'; +import { LoadingIndicator } from '@/mastodon/components/loading_indicator'; +import { patchProfile } from '@/mastodon/reducers/slices/profile_edit'; +import { useAppDispatch, useAppSelector } from '@/mastodon/store'; + +import type { DialogModalProps } from '../../ui/components/dialog_modal'; +import { DialogModal } from '../../ui/components/dialog_modal'; +import { messages } from '../index'; +import classes from '../styles.module.scss'; + +export const ProfileDisplayModal: FC = ({ onClose }) => { + const intl = useIntl(); + + const { profile, isPending } = useAppSelector((state) => state.profileEdit); + const serverName = useAppSelector( + (state) => state.meta.get('domain') as string, + ); + + const dispatch = useAppDispatch(); + const handleToggleChange: ChangeEventHandler = useCallback( + (event) => { + const { name, checked } = event.target; + void dispatch(patchProfile({ [name]: checked })); + }, + [dispatch], + ); + + if (!profile) { + return ; + } + + return ( + +
+ + } + hint={ + + } + /> + + + } + hint={ + + } + /> + + + } + hint={ + + } + /> +
+ + + } + icon={false} + > + + +
+ ); +}; diff --git a/app/javascript/mastodon/features/account_edit/index.tsx b/app/javascript/mastodon/features/account_edit/index.tsx index c8bc93f15f..da58088e89 100644 --- a/app/javascript/mastodon/features/account_edit/index.tsx +++ b/app/javascript/mastodon/features/account_edit/index.tsx @@ -1,13 +1,14 @@ import { useCallback, useEffect } from 'react'; import type { FC } from 'react'; -import { defineMessages, useIntl } from 'react-intl'; +import { defineMessages, FormattedMessage, useIntl } from 'react-intl'; import { useHistory } from 'react-router-dom'; import type { ModalType } from '@/mastodon/actions/modal'; import { openModal } from '@/mastodon/actions/modal'; import { Avatar } from '@/mastodon/components/avatar'; +import { Button } from '@/mastodon/components/button'; import { CustomEmojiProvider } from '@/mastodon/components/emoji/context'; import { EmojiHTML } from '@/mastodon/components/emoji/html'; import { useElementHandledLink } from '@/mastodon/components/status/handled_link'; @@ -25,7 +26,7 @@ import { EditButton } from './components/edit_button'; import { AccountEditSection } from './components/section'; import classes from './styles.module.scss'; -const messages = defineMessages({ +export const messages = defineMessages({ columnTitle: { id: 'account_edit.column_title', defaultMessage: 'Edit Profile', @@ -104,6 +105,9 @@ export const AccountEdit: FC = () => { const handleBioEdit = useCallback(() => { handleOpenModal('ACCOUNT_EDIT_BIO'); }, [handleOpenModal]); + const handleProfileDisplayEdit = useCallback(() => { + handleOpenModal('ACCOUNT_EDIT_PROFILE_DISPLAY'); + }, [handleOpenModal]); const history = useHistory(); const handleFeaturedTagsEdit = useCallback(() => { @@ -193,6 +197,17 @@ export const AccountEdit: FC = () => { title={messages.profileTabTitle} description={messages.profileTabSubtitle} showDescription + buttons={ + + } /> diff --git a/app/javascript/mastodon/features/account_edit/styles.module.scss b/app/javascript/mastodon/features/account_edit/styles.module.scss index ee8603cc4f..29daddbe3f 100644 --- a/app/javascript/mastodon/features/account_edit/styles.module.scss +++ b/app/javascript/mastodon/features/account_edit/styles.module.scss @@ -90,6 +90,16 @@ textarea.inputText { } } +.toggleInputWrapper { + > div { + padding: 12px 0; + + &:not(:first-child) { + border-top: 1px solid var(--color-border-primary); + } + } +} + // Column component .column { diff --git a/app/javascript/mastodon/features/ui/components/dialog_modal.tsx b/app/javascript/mastodon/features/ui/components/dialog_modal.tsx new file mode 100644 index 0000000000..8c850fa0d3 --- /dev/null +++ b/app/javascript/mastodon/features/ui/components/dialog_modal.tsx @@ -0,0 +1,94 @@ +import type { FC, ReactNode } from 'react'; + +import { FormattedMessage, useIntl } from 'react-intl'; + +import classNames from 'classnames'; + +import { Button } from '@/mastodon/components/button'; +import { IconButton } from '@/mastodon/components/icon_button'; +import CloseIcon from '@/material-icons/400-24px/close.svg?react'; + +export type { BaseConfirmationModalProps as DialogModalProps } from './confirmation_modals/confirmation_modal'; + +interface DialogModalProps { + className?: string; + title: ReactNode; + onClose: () => void; + description?: ReactNode; + formClassName?: string; + children?: ReactNode; + noCancelButton?: boolean; + onSave?: () => void; + saveLabel?: ReactNode; +} + +export const DialogModal: FC = ({ + className, + title, + onClose, + description, + formClassName, + children, + noCancelButton = false, + onSave, + saveLabel, +}) => { + const intl = useIntl(); + + const showButtons = !noCancelButton || onSave; + + return ( +
+
+ + +

{title}

+
+ +
+ {description && ( +
+ {description} +
+ )} +
+ {children} +
+
+ + {showButtons && ( +
+ {!noCancelButton && ( + + )} + {onSave && ( + + )} +
+ )} +
+ ); +}; diff --git a/app/javascript/mastodon/features/ui/components/modal_root.jsx b/app/javascript/mastodon/features/ui/components/modal_root.jsx index 3e2751c7a3..6086858e3d 100644 --- a/app/javascript/mastodon/features/ui/components/modal_root.jsx +++ b/app/javascript/mastodon/features/ui/components/modal_root.jsx @@ -97,6 +97,7 @@ export const MODAL_COMPONENTS = { 'ACCOUNT_FIELD_OVERFLOW': () => import('@/mastodon/features/account_timeline/modals/field_modal').then(module => ({ default: module.AccountFieldModal })), 'ACCOUNT_EDIT_NAME': () => import('@/mastodon/features/account_edit/components/name_modal').then(module => ({ default: module.NameModal })), 'ACCOUNT_EDIT_BIO': () => import('@/mastodon/features/account_edit/components/bio_modal').then(module => ({ default: module.BioModal })), + 'ACCOUNT_EDIT_PROFILE_DISPLAY': () => import('@/mastodon/features/account_edit/components/profile_display_modal').then(module => ({ default: module.ProfileDisplayModal })), }; export default class ModalRoot extends PureComponent { diff --git a/app/javascript/mastodon/locales/en.json b/app/javascript/mastodon/locales/en.json index 32088474eb..03b7a5d2c4 100644 --- a/app/javascript/mastodon/locales/en.json +++ b/app/javascript/mastodon/locales/en.json @@ -161,6 +161,15 @@ "account_edit.featured_hashtags.title": "Featured hashtags", "account_edit.name_modal.add_title": "Add display name", "account_edit.name_modal.edit_title": "Edit display name", + "account_edit.profile_tab.button_label": "Customize", + "account_edit.profile_tab.hint.description": "These settings customize what users see on {server} in the official apps, but they may not apply to users on other servers and 3rd party apps.", + "account_edit.profile_tab.hint.title": "Displays still vary", + "account_edit.profile_tab.show_featured.description": "‘Featured’ is an optional tab where you can showcase other accounts.", + "account_edit.profile_tab.show_featured.title": "Show ‘Featured’ tab", + "account_edit.profile_tab.show_media.description": "‘Media’ is an optional tab that shows your posts containing images or videos.", + "account_edit.profile_tab.show_media.title": "Show ‘Media’ tab", + "account_edit.profile_tab.show_media_replies.description": "When enabled, Media tab shows both your posts and replies to other people’s posts.", + "account_edit.profile_tab.show_media_replies.title": "Include replies on ‘Media’ tab", "account_edit.profile_tab.subtitle": "Customize the tabs on your profile and what they display.", "account_edit.profile_tab.title": "Profile tab settings", "account_edit.save": "Save",