|
1 | | -import type { FC } from 'react'; |
| 1 | +import type { ChangeEventHandler, FC } from 'react'; |
| 2 | +import { useCallback, useState } from 'react'; |
2 | 3 |
|
| 4 | +import { FormattedMessage } from 'react-intl'; |
| 5 | + |
| 6 | +import { CharacterCounter } from '@/flavours/glitch/components/character_counter'; |
| 7 | +import { Details } from '@/flavours/glitch/components/details'; |
| 8 | +import { TextAreaField } from '@/flavours/glitch/components/form_fields'; |
| 9 | +import { LoadingIndicator } from '@/flavours/glitch/components/loading_indicator'; |
| 10 | +import { patchProfile } from '@/flavours/glitch/reducers/slices/profile_edit'; |
3 | 11 | import type { ImageLocation } from '@/flavours/glitch/reducers/slices/profile_edit'; |
| 12 | +import { useAppDispatch, useAppSelector } from '@/flavours/glitch/store'; |
4 | 13 |
|
5 | | -import { DialogModal } from '../../ui/components/dialog_modal'; |
| 14 | +import { ConfirmationModal } from '../../ui/components/confirmation_modals'; |
6 | 15 | import type { DialogModalProps } from '../../ui/components/dialog_modal'; |
7 | 16 |
|
| 17 | +import classes from './styles.module.scss'; |
| 18 | + |
8 | 19 | export const ImageAltModal: FC< |
9 | 20 | DialogModalProps & { location: ImageLocation } |
10 | | -> = ({ onClose }) => { |
11 | | - return <DialogModal title='TODO' onClose={onClose} />; |
| 21 | +> = ({ onClose, location }) => { |
| 22 | + const { profile, isPending } = useAppSelector((state) => state.profileEdit); |
| 23 | + |
| 24 | + const initialAlt = profile?.[`${location}Description`]; |
| 25 | + const imageSrc = profile?.[`${location}Static`]; |
| 26 | + |
| 27 | + const [altText, setAltText] = useState(initialAlt ?? ''); |
| 28 | + |
| 29 | + const dispatch = useAppDispatch(); |
| 30 | + const handleSave = useCallback(() => { |
| 31 | + void dispatch( |
| 32 | + patchProfile({ |
| 33 | + [`${location}_description`]: altText, |
| 34 | + }), |
| 35 | + ).then(onClose); |
| 36 | + }, [altText, dispatch, location, onClose]); |
| 37 | + |
| 38 | + if (!imageSrc) { |
| 39 | + return <LoadingIndicator />; |
| 40 | + } |
| 41 | + |
| 42 | + return ( |
| 43 | + <ConfirmationModal |
| 44 | + title={ |
| 45 | + initialAlt ? ( |
| 46 | + <FormattedMessage |
| 47 | + id='account_edit.image_alt_modal.edit_title' |
| 48 | + defaultMessage='Edit alt text' |
| 49 | + /> |
| 50 | + ) : ( |
| 51 | + <FormattedMessage |
| 52 | + id='account_edit.image_alt_modal.add_title' |
| 53 | + defaultMessage='Add alt text' |
| 54 | + /> |
| 55 | + ) |
| 56 | + } |
| 57 | + onClose={onClose} |
| 58 | + onConfirm={handleSave} |
| 59 | + confirm={ |
| 60 | + <FormattedMessage |
| 61 | + id='account_edit.upload_modal.done' |
| 62 | + defaultMessage='Done' |
| 63 | + /> |
| 64 | + } |
| 65 | + updating={isPending} |
| 66 | + > |
| 67 | + <div className={classes.wrapper}> |
| 68 | + <ImageAltTextField |
| 69 | + imageSrc={imageSrc} |
| 70 | + altText={altText} |
| 71 | + onChange={setAltText} |
| 72 | + /> |
| 73 | + </div> |
| 74 | + </ConfirmationModal> |
| 75 | + ); |
| 76 | +}; |
| 77 | + |
| 78 | +export const ImageAltTextField: FC<{ |
| 79 | + imageSrc: string; |
| 80 | + altText: string; |
| 81 | + onChange: (altText: string) => void; |
| 82 | +}> = ({ imageSrc, altText, onChange }) => { |
| 83 | + const altLimit = useAppSelector( |
| 84 | + (state) => |
| 85 | + state.server.getIn( |
| 86 | + ['server', 'configuration', 'media_attachments', 'description_limit'], |
| 87 | + 150, |
| 88 | + ) as number, |
| 89 | + ); |
| 90 | + |
| 91 | + const handleChange: ChangeEventHandler<HTMLTextAreaElement> = useCallback( |
| 92 | + (event) => { |
| 93 | + onChange(event.currentTarget.value); |
| 94 | + }, |
| 95 | + [onChange], |
| 96 | + ); |
| 97 | + |
| 98 | + return ( |
| 99 | + <> |
| 100 | + <img src={imageSrc} alt='' className={classes.altImage} /> |
| 101 | + |
| 102 | + <div> |
| 103 | + <TextAreaField |
| 104 | + label={ |
| 105 | + <FormattedMessage |
| 106 | + id='account_edit.image_alt_modal.text_label' |
| 107 | + defaultMessage='Alt text' |
| 108 | + /> |
| 109 | + } |
| 110 | + hint={ |
| 111 | + <FormattedMessage |
| 112 | + id='account_edit.image_alt_modal.text_hint' |
| 113 | + defaultMessage='Alt text helps screen reader users to understand your content.' |
| 114 | + /> |
| 115 | + } |
| 116 | + onChange={handleChange} |
| 117 | + value={altText} |
| 118 | + /> |
| 119 | + <CharacterCounter |
| 120 | + currentString={altText} |
| 121 | + maxLength={altLimit} |
| 122 | + className={classes.altCounter} |
| 123 | + /> |
| 124 | + </div> |
| 125 | + |
| 126 | + <Details |
| 127 | + summary={ |
| 128 | + <FormattedMessage |
| 129 | + id='account_edit.image_alt_modal.details_title' |
| 130 | + defaultMessage='Tips: Alt text for profile photos' |
| 131 | + /> |
| 132 | + } |
| 133 | + className={classes.altHint} |
| 134 | + > |
| 135 | + <FormattedMessage |
| 136 | + id='account_edit.image_alt_modal.details_content' |
| 137 | + defaultMessage='DO: <ul> <li>Describe yourself as pictured</li> <li>Use third person language (e.g. “Alex” instead of “me”)</li> <li>Be succinct – a few words is often enough</li> </ul> DON’T: <ul> <li>Start with “Photo of” – it’s redundant for screen readers</li> </ul> EXAMPLE: <ul> <li>“Alex wearing a green shirt and glasses”</li> </ul>' |
| 138 | + values={{ |
| 139 | + ul: (chunks) => <ul>{chunks}</ul>, |
| 140 | + li: (chunks) => <li>{chunks}</li>, |
| 141 | + }} |
| 142 | + tagName='div' |
| 143 | + /> |
| 144 | + </Details> |
| 145 | + </> |
| 146 | + ); |
12 | 147 | }; |
0 commit comments