Revamp edit image alt text dialog (#5461)
* revamp alt dialog * readd the limit check don't trim with enforceLen, it ruins copy-pasting long text and it's overall annoying behavior * Update src/view/com/composer/photos/ImageAltTextDialog.tsx Co-authored-by: surfdude29 <149612116+surfdude29@users.noreply.github.com> --------- Co-authored-by: surfdude29 <149612116+surfdude29@users.noreply.github.com>
This commit is contained in:
@@ -3,7 +3,6 @@ import {Image as RNImage} from 'react-native-image-crop-picker'
|
|||||||
import {AppBskyActorDefs, AppBskyGraphDefs} from '@atproto/api'
|
import {AppBskyActorDefs, AppBskyGraphDefs} from '@atproto/api'
|
||||||
|
|
||||||
import {useNonReactiveCallback} from '#/lib/hooks/useNonReactiveCallback'
|
import {useNonReactiveCallback} from '#/lib/hooks/useNonReactiveCallback'
|
||||||
import {ComposerImage} from '../gallery'
|
|
||||||
|
|
||||||
export interface EditProfileModal {
|
export interface EditProfileModal {
|
||||||
name: 'edit-profile'
|
name: 'edit-profile'
|
||||||
@@ -43,12 +42,6 @@ export interface CropImageModal {
|
|||||||
onSelect: (img?: RNImage) => void
|
onSelect: (img?: RNImage) => void
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface AltTextImageModal {
|
|
||||||
name: 'alt-text-image'
|
|
||||||
image: ComposerImage
|
|
||||||
onChange: (next: ComposerImage) => void
|
|
||||||
}
|
|
||||||
|
|
||||||
export interface DeleteAccountModal {
|
export interface DeleteAccountModal {
|
||||||
name: 'delete-account'
|
name: 'delete-account'
|
||||||
}
|
}
|
||||||
@@ -131,7 +124,6 @@ export type Modal =
|
|||||||
| ListAddRemoveUsersModal
|
| ListAddRemoveUsersModal
|
||||||
|
|
||||||
// Posts
|
// Posts
|
||||||
| AltTextImageModal
|
|
||||||
| CropImageModal
|
| CropImageModal
|
||||||
| SelfLabelModal
|
| SelfLabelModal
|
||||||
|
|
||||||
|
|||||||
@@ -18,9 +18,10 @@ import {Dimensions} from '#/lib/media/types'
|
|||||||
import {colors, s} from '#/lib/styles'
|
import {colors, s} from '#/lib/styles'
|
||||||
import {isNative} from '#/platform/detection'
|
import {isNative} from '#/platform/detection'
|
||||||
import {ComposerImage, cropImage} from '#/state/gallery'
|
import {ComposerImage, cropImage} from '#/state/gallery'
|
||||||
import {useModalControls} from '#/state/modals'
|
|
||||||
import {Text} from '#/view/com/util/text/Text'
|
import {Text} from '#/view/com/util/text/Text'
|
||||||
import {useTheme} from '#/alf'
|
import {useTheme} from '#/alf'
|
||||||
|
import * as Dialog from '#/components/Dialog'
|
||||||
|
import {ImageAltTextDialog} from './ImageAltTextDialog'
|
||||||
|
|
||||||
const IMAGE_GAP = 8
|
const IMAGE_GAP = 8
|
||||||
|
|
||||||
@@ -141,7 +142,8 @@ const GalleryItem = ({
|
|||||||
}: GalleryItemProps): React.ReactNode => {
|
}: GalleryItemProps): React.ReactNode => {
|
||||||
const {_} = useLingui()
|
const {_} = useLingui()
|
||||||
const t = useTheme()
|
const t = useTheme()
|
||||||
const {openModal} = useModalControls()
|
|
||||||
|
const altTextControl = Dialog.useDialogControl()
|
||||||
|
|
||||||
const onImageEdit = () => {
|
const onImageEdit = () => {
|
||||||
if (isNative) {
|
if (isNative) {
|
||||||
@@ -153,7 +155,7 @@ const GalleryItem = ({
|
|||||||
|
|
||||||
const onAltTextEdit = () => {
|
const onAltTextEdit = () => {
|
||||||
Keyboard.dismiss()
|
Keyboard.dismiss()
|
||||||
openModal({name: 'alt-text-image', image, onChange})
|
altTextControl.open()
|
||||||
}
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
@@ -229,6 +231,12 @@ const GalleryItem = ({
|
|||||||
accessible={true}
|
accessible={true}
|
||||||
accessibilityIgnoresInvertColors
|
accessibilityIgnoresInvertColors
|
||||||
/>
|
/>
|
||||||
|
|
||||||
|
<ImageAltTextDialog
|
||||||
|
control={altTextControl}
|
||||||
|
image={image}
|
||||||
|
onChange={onChange}
|
||||||
|
/>
|
||||||
</View>
|
</View>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,121 @@
|
|||||||
|
import React from 'react'
|
||||||
|
import {ImageStyle, useWindowDimensions, View} from 'react-native'
|
||||||
|
import {Image} from 'expo-image'
|
||||||
|
import {msg, Trans} from '@lingui/macro'
|
||||||
|
import {useLingui} from '@lingui/react'
|
||||||
|
|
||||||
|
import {MAX_ALT_TEXT} from '#/lib/constants'
|
||||||
|
import {isWeb} from '#/platform/detection'
|
||||||
|
import {ComposerImage} from '#/state/gallery'
|
||||||
|
import {atoms as a, useTheme} from '#/alf'
|
||||||
|
import {Button, ButtonText} from '#/components/Button'
|
||||||
|
import * as Dialog from '#/components/Dialog'
|
||||||
|
import * as TextField from '#/components/forms/TextField'
|
||||||
|
import {Text} from '#/components/Typography'
|
||||||
|
|
||||||
|
type Props = {
|
||||||
|
control: Dialog.DialogOuterProps['control']
|
||||||
|
image: ComposerImage
|
||||||
|
onChange: (next: ComposerImage) => void
|
||||||
|
}
|
||||||
|
|
||||||
|
export const ImageAltTextDialog = (props: Props): React.ReactNode => {
|
||||||
|
return (
|
||||||
|
<Dialog.Outer control={props.control}>
|
||||||
|
<Dialog.Handle />
|
||||||
|
|
||||||
|
<ImageAltTextInner {...props} />
|
||||||
|
</Dialog.Outer>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
const ImageAltTextInner = ({
|
||||||
|
control,
|
||||||
|
image,
|
||||||
|
onChange,
|
||||||
|
}: Props): React.ReactNode => {
|
||||||
|
const {_} = useLingui()
|
||||||
|
const t = useTheme()
|
||||||
|
|
||||||
|
const windim = useWindowDimensions()
|
||||||
|
|
||||||
|
const [altText, setAltText] = React.useState(image.alt)
|
||||||
|
|
||||||
|
const onPressSubmit = React.useCallback(() => {
|
||||||
|
control.close()
|
||||||
|
onChange({...image, alt: altText.trim()})
|
||||||
|
}, [control, image, altText, onChange])
|
||||||
|
|
||||||
|
const imageStyle = React.useMemo<ImageStyle>(() => {
|
||||||
|
const maxWidth = isWeb ? 450 : windim.width
|
||||||
|
const source = image.transformed ?? image.source
|
||||||
|
|
||||||
|
if (source.height > source.width) {
|
||||||
|
return {
|
||||||
|
resizeMode: 'contain',
|
||||||
|
width: '100%',
|
||||||
|
aspectRatio: 1,
|
||||||
|
borderRadius: 8,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return {
|
||||||
|
width: '100%',
|
||||||
|
height: (maxWidth / source.width) * source.height,
|
||||||
|
borderRadius: 8,
|
||||||
|
}
|
||||||
|
}, [image, windim])
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Dialog.ScrollableInner label={_(msg`Add alt text`)}>
|
||||||
|
<Dialog.Close />
|
||||||
|
|
||||||
|
<View>
|
||||||
|
<Text style={[a.text_2xl, a.font_bold, a.leading_tight, a.pb_sm]}>
|
||||||
|
<Trans>Add alt text</Trans>
|
||||||
|
</Text>
|
||||||
|
|
||||||
|
<View style={[t.atoms.bg_contrast_50, a.rounded_sm, a.overflow_hidden]}>
|
||||||
|
<Image
|
||||||
|
style={imageStyle}
|
||||||
|
source={{
|
||||||
|
uri: (image.transformed ?? image.source).path,
|
||||||
|
}}
|
||||||
|
contentFit="contain"
|
||||||
|
accessible={true}
|
||||||
|
accessibilityIgnoresInvertColors
|
||||||
|
enableLiveTextInteraction
|
||||||
|
/>
|
||||||
|
</View>
|
||||||
|
</View>
|
||||||
|
|
||||||
|
<View style={[a.mt_md, a.gap_md]}>
|
||||||
|
<View>
|
||||||
|
<TextField.LabelText>
|
||||||
|
<Trans>Descriptive alt text</Trans>
|
||||||
|
</TextField.LabelText>
|
||||||
|
<TextField.Root>
|
||||||
|
<Dialog.Input
|
||||||
|
label={_(msg`Alt text`)}
|
||||||
|
onChangeText={text => setAltText(text)}
|
||||||
|
value={altText}
|
||||||
|
multiline
|
||||||
|
numberOfLines={3}
|
||||||
|
autoFocus
|
||||||
|
/>
|
||||||
|
</TextField.Root>
|
||||||
|
</View>
|
||||||
|
<Button
|
||||||
|
label={_(msg`Save`)}
|
||||||
|
disabled={altText.length > MAX_ALT_TEXT || altText === image.alt}
|
||||||
|
size="large"
|
||||||
|
color="primary"
|
||||||
|
variant="solid"
|
||||||
|
onPress={onPressSubmit}>
|
||||||
|
<ButtonText>
|
||||||
|
<Trans>Save</Trans>
|
||||||
|
</ButtonText>
|
||||||
|
</Button>
|
||||||
|
</View>
|
||||||
|
</Dialog.ScrollableInner>
|
||||||
|
)
|
||||||
|
}
|
||||||
@@ -1,189 +0,0 @@
|
|||||||
import React, {useCallback, useMemo, useState} from 'react'
|
|
||||||
import {
|
|
||||||
ImageStyle,
|
|
||||||
ScrollView as RNScrollView,
|
|
||||||
StyleSheet,
|
|
||||||
TextInput as RNTextInput,
|
|
||||||
TouchableOpacity,
|
|
||||||
useWindowDimensions,
|
|
||||||
View,
|
|
||||||
} from 'react-native'
|
|
||||||
import {Image} from 'expo-image'
|
|
||||||
import {LinearGradient} from 'expo-linear-gradient'
|
|
||||||
import {msg, Trans} from '@lingui/macro'
|
|
||||||
import {useLingui} from '@lingui/react'
|
|
||||||
|
|
||||||
import {ComposerImage} from '#/state/gallery'
|
|
||||||
import {useModalControls} from '#/state/modals'
|
|
||||||
import {MAX_ALT_TEXT} from 'lib/constants'
|
|
||||||
import {useIsKeyboardVisible} from 'lib/hooks/useIsKeyboardVisible'
|
|
||||||
import {usePalette} from 'lib/hooks/usePalette'
|
|
||||||
import {enforceLen} from 'lib/strings/helpers'
|
|
||||||
import {gradients, s} from 'lib/styles'
|
|
||||||
import {useTheme} from 'lib/ThemeContext'
|
|
||||||
import {isAndroid, isWeb} from 'platform/detection'
|
|
||||||
import {Text} from '../util/text/Text'
|
|
||||||
import {ScrollView, TextInput} from './util'
|
|
||||||
|
|
||||||
export const snapPoints = ['100%']
|
|
||||||
|
|
||||||
interface Props {
|
|
||||||
image: ComposerImage
|
|
||||||
onChange: (next: ComposerImage) => void
|
|
||||||
}
|
|
||||||
|
|
||||||
export function Component({image, onChange}: Props) {
|
|
||||||
const pal = usePalette('default')
|
|
||||||
const theme = useTheme()
|
|
||||||
const {_} = useLingui()
|
|
||||||
const [altText, setAltText] = useState(image.alt)
|
|
||||||
const windim = useWindowDimensions()
|
|
||||||
const {closeModal} = useModalControls()
|
|
||||||
const inputRef = React.useRef<RNTextInput>(null)
|
|
||||||
const scrollViewRef = React.useRef<RNScrollView>(null)
|
|
||||||
const keyboardShown = useIsKeyboardVisible()
|
|
||||||
|
|
||||||
// Autofocus hack when we open the modal. We have to wait for the animation to complete first
|
|
||||||
React.useEffect(() => {
|
|
||||||
if (isAndroid) return
|
|
||||||
setTimeout(() => {
|
|
||||||
inputRef.current?.focus()
|
|
||||||
}, 500)
|
|
||||||
}, [])
|
|
||||||
|
|
||||||
// We'd rather be at the bottom here so that we can easily dismiss the modal instead of having to scroll
|
|
||||||
// (especially on android, it acts weird)
|
|
||||||
React.useEffect(() => {
|
|
||||||
if (keyboardShown[0]) {
|
|
||||||
scrollViewRef.current?.scrollToEnd()
|
|
||||||
}
|
|
||||||
}, [keyboardShown])
|
|
||||||
|
|
||||||
const imageStyles = useMemo<ImageStyle>(() => {
|
|
||||||
const maxWidth = isWeb ? 450 : windim.width
|
|
||||||
const media = image.transformed ?? image.source
|
|
||||||
if (media.height > media.width) {
|
|
||||||
return {
|
|
||||||
resizeMode: 'contain',
|
|
||||||
width: '100%',
|
|
||||||
aspectRatio: 1,
|
|
||||||
borderRadius: 8,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return {
|
|
||||||
width: '100%',
|
|
||||||
height: (maxWidth / media.width) * media.height,
|
|
||||||
borderRadius: 8,
|
|
||||||
}
|
|
||||||
}, [image, windim])
|
|
||||||
|
|
||||||
const onUpdate = useCallback(
|
|
||||||
(v: string) => {
|
|
||||||
v = enforceLen(v, MAX_ALT_TEXT)
|
|
||||||
setAltText(v)
|
|
||||||
},
|
|
||||||
[setAltText],
|
|
||||||
)
|
|
||||||
|
|
||||||
const onPressSave = useCallback(() => {
|
|
||||||
onChange({
|
|
||||||
...image,
|
|
||||||
alt: altText,
|
|
||||||
})
|
|
||||||
|
|
||||||
closeModal()
|
|
||||||
}, [closeModal, image, altText, onChange])
|
|
||||||
|
|
||||||
return (
|
|
||||||
<ScrollView
|
|
||||||
testID="altTextImageModal"
|
|
||||||
style={[pal.view, styles.scrollContainer]}
|
|
||||||
keyboardShouldPersistTaps="always"
|
|
||||||
ref={scrollViewRef}
|
|
||||||
nativeID="imageAltText">
|
|
||||||
<View style={styles.scrollInner}>
|
|
||||||
<View style={[pal.viewLight, styles.imageContainer]}>
|
|
||||||
<Image
|
|
||||||
testID="selectedPhotoImage"
|
|
||||||
style={imageStyles}
|
|
||||||
source={{uri: (image.transformed ?? image.source).path}}
|
|
||||||
contentFit="contain"
|
|
||||||
accessible={true}
|
|
||||||
accessibilityIgnoresInvertColors
|
|
||||||
enableLiveTextInteraction
|
|
||||||
/>
|
|
||||||
</View>
|
|
||||||
<TextInput
|
|
||||||
testID="altTextImageInput"
|
|
||||||
style={[styles.textArea, pal.border, pal.text]}
|
|
||||||
keyboardAppearance={theme.colorScheme}
|
|
||||||
multiline
|
|
||||||
placeholder={_(msg`Add alt text`)}
|
|
||||||
placeholderTextColor={pal.colors.textLight}
|
|
||||||
value={altText}
|
|
||||||
onChangeText={onUpdate}
|
|
||||||
accessibilityLabel={_(msg`Image alt text`)}
|
|
||||||
accessibilityHint=""
|
|
||||||
accessibilityLabelledBy="imageAltText"
|
|
||||||
// @ts-ignore This is fine, type is weird on the BottomSheetTextInput
|
|
||||||
ref={inputRef}
|
|
||||||
/>
|
|
||||||
<View style={styles.buttonControls}>
|
|
||||||
<TouchableOpacity
|
|
||||||
testID="altTextImageSaveBtn"
|
|
||||||
onPress={onPressSave}
|
|
||||||
accessibilityLabel={_(msg`Save alt text`)}
|
|
||||||
accessibilityHint=""
|
|
||||||
accessibilityRole="button">
|
|
||||||
<LinearGradient
|
|
||||||
colors={[gradients.blueLight.start, gradients.blueLight.end]}
|
|
||||||
start={{x: 0, y: 0}}
|
|
||||||
end={{x: 1, y: 1}}
|
|
||||||
style={[styles.button]}>
|
|
||||||
<Text type="button-lg" style={[s.white, s.bold]}>
|
|
||||||
<Trans>Done</Trans>
|
|
||||||
</Text>
|
|
||||||
</LinearGradient>
|
|
||||||
</TouchableOpacity>
|
|
||||||
</View>
|
|
||||||
</View>
|
|
||||||
</ScrollView>
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
const styles = StyleSheet.create({
|
|
||||||
scrollContainer: {
|
|
||||||
flex: 1,
|
|
||||||
height: '100%',
|
|
||||||
paddingHorizontal: isWeb ? 0 : 12,
|
|
||||||
paddingVertical: isWeb ? 0 : 24,
|
|
||||||
},
|
|
||||||
scrollInner: {
|
|
||||||
gap: 12,
|
|
||||||
paddingTop: isWeb ? 0 : 12,
|
|
||||||
},
|
|
||||||
imageContainer: {
|
|
||||||
borderRadius: 8,
|
|
||||||
},
|
|
||||||
textArea: {
|
|
||||||
borderWidth: 1,
|
|
||||||
borderRadius: 6,
|
|
||||||
paddingTop: 10,
|
|
||||||
paddingHorizontal: 12,
|
|
||||||
fontSize: 16,
|
|
||||||
height: 100,
|
|
||||||
textAlignVertical: 'top',
|
|
||||||
},
|
|
||||||
button: {
|
|
||||||
flexDirection: 'row',
|
|
||||||
alignItems: 'center',
|
|
||||||
justifyContent: 'center',
|
|
||||||
width: '100%',
|
|
||||||
borderRadius: 32,
|
|
||||||
padding: 10,
|
|
||||||
},
|
|
||||||
buttonControls: {
|
|
||||||
gap: 8,
|
|
||||||
paddingBottom: isWeb ? 0 : 50,
|
|
||||||
},
|
|
||||||
})
|
|
||||||
@@ -3,12 +3,11 @@ import {StyleSheet} from 'react-native'
|
|||||||
import {SafeAreaView} from 'react-native-safe-area-context'
|
import {SafeAreaView} from 'react-native-safe-area-context'
|
||||||
import BottomSheet from '@discord/bottom-sheet/src'
|
import BottomSheet from '@discord/bottom-sheet/src'
|
||||||
|
|
||||||
|
import {usePalette} from '#/lib/hooks/usePalette'
|
||||||
import {useModalControls, useModals} from '#/state/modals'
|
import {useModalControls, useModals} from '#/state/modals'
|
||||||
import {usePalette} from 'lib/hooks/usePalette'
|
|
||||||
import {FullWindowOverlay} from '#/components/FullWindowOverlay'
|
import {FullWindowOverlay} from '#/components/FullWindowOverlay'
|
||||||
import {createCustomBackdrop} from '../util/BottomSheetCustomBackdrop'
|
import {createCustomBackdrop} from '../util/BottomSheetCustomBackdrop'
|
||||||
import * as AddAppPassword from './AddAppPasswords'
|
import * as AddAppPassword from './AddAppPasswords'
|
||||||
import * as AltImageModal from './AltImage'
|
|
||||||
import * as ChangeEmailModal from './ChangeEmail'
|
import * as ChangeEmailModal from './ChangeEmail'
|
||||||
import * as ChangeHandleModal from './ChangeHandle'
|
import * as ChangeHandleModal from './ChangeHandle'
|
||||||
import * as ChangePasswordModal from './ChangePassword'
|
import * as ChangePasswordModal from './ChangePassword'
|
||||||
@@ -74,9 +73,6 @@ export function ModalsContainer() {
|
|||||||
} else if (activeModal?.name === 'self-label') {
|
} else if (activeModal?.name === 'self-label') {
|
||||||
snapPoints = SelfLabelModal.snapPoints
|
snapPoints = SelfLabelModal.snapPoints
|
||||||
element = <SelfLabelModal.Component {...activeModal} />
|
element = <SelfLabelModal.Component {...activeModal} />
|
||||||
} else if (activeModal?.name === 'alt-text-image') {
|
|
||||||
snapPoints = AltImageModal.snapPoints
|
|
||||||
element = <AltImageModal.Component {...activeModal} />
|
|
||||||
} else if (activeModal?.name === 'change-handle') {
|
} else if (activeModal?.name === 'change-handle') {
|
||||||
snapPoints = ChangeHandleModal.snapPoints
|
snapPoints = ChangeHandleModal.snapPoints
|
||||||
element = <ChangeHandleModal.Component {...activeModal} />
|
element = <ChangeHandleModal.Component {...activeModal} />
|
||||||
|
|||||||
@@ -2,13 +2,12 @@ import React from 'react'
|
|||||||
import {StyleSheet, TouchableWithoutFeedback, View} from 'react-native'
|
import {StyleSheet, TouchableWithoutFeedback, View} from 'react-native'
|
||||||
import Animated, {FadeIn, FadeOut} from 'react-native-reanimated'
|
import Animated, {FadeIn, FadeOut} from 'react-native-reanimated'
|
||||||
|
|
||||||
|
import {usePalette} from '#/lib/hooks/usePalette'
|
||||||
import {useWebBodyScrollLock} from '#/lib/hooks/useWebBodyScrollLock'
|
import {useWebBodyScrollLock} from '#/lib/hooks/useWebBodyScrollLock'
|
||||||
|
import {useWebMediaQueries} from '#/lib/hooks/useWebMediaQueries'
|
||||||
import type {Modal as ModalIface} from '#/state/modals'
|
import type {Modal as ModalIface} from '#/state/modals'
|
||||||
import {useModalControls, useModals} from '#/state/modals'
|
import {useModalControls, useModals} from '#/state/modals'
|
||||||
import {usePalette} from 'lib/hooks/usePalette'
|
|
||||||
import {useWebMediaQueries} from 'lib/hooks/useWebMediaQueries'
|
|
||||||
import * as AddAppPassword from './AddAppPasswords'
|
import * as AddAppPassword from './AddAppPasswords'
|
||||||
import * as AltTextImageModal from './AltImage'
|
|
||||||
import * as ChangeEmailModal from './ChangeEmail'
|
import * as ChangeEmailModal from './ChangeEmail'
|
||||||
import * as ChangeHandleModal from './ChangeHandle'
|
import * as ChangeHandleModal from './ChangeHandle'
|
||||||
import * as ChangePasswordModal from './ChangePassword'
|
import * as ChangePasswordModal from './ChangePassword'
|
||||||
@@ -53,7 +52,7 @@ function Modal({modal}: {modal: ModalIface}) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
const onPressMask = () => {
|
const onPressMask = () => {
|
||||||
if (modal.name === 'crop-image' || modal.name === 'alt-text-image') {
|
if (modal.name === 'crop-image') {
|
||||||
return // dont close on mask presses during crop
|
return // dont close on mask presses during crop
|
||||||
}
|
}
|
||||||
closeModal()
|
closeModal()
|
||||||
@@ -88,8 +87,6 @@ function Modal({modal}: {modal: ModalIface}) {
|
|||||||
element = <ContentLanguagesSettingsModal.Component />
|
element = <ContentLanguagesSettingsModal.Component />
|
||||||
} else if (modal.name === 'post-languages-settings') {
|
} else if (modal.name === 'post-languages-settings') {
|
||||||
element = <PostLanguagesSettingsModal.Component />
|
element = <PostLanguagesSettingsModal.Component />
|
||||||
} else if (modal.name === 'alt-text-image') {
|
|
||||||
element = <AltTextImageModal.Component {...modal} />
|
|
||||||
} else if (modal.name === 'verify-email') {
|
} else if (modal.name === 'verify-email') {
|
||||||
element = <VerifyEmailModal.Component {...modal} />
|
element = <VerifyEmailModal.Component {...modal} />
|
||||||
} else if (modal.name === 'change-email') {
|
} else if (modal.name === 'change-email') {
|
||||||
|
|||||||
Reference in New Issue
Block a user