diff --git a/app/soapbox/components/ui/progress-bar/progress-bar.tsx b/app/soapbox/components/ui/progress-bar/progress-bar.tsx index c0161ae34..0507045e4 100644 --- a/app/soapbox/components/ui/progress-bar/progress-bar.tsx +++ b/app/soapbox/components/ui/progress-bar/progress-bar.tsx @@ -1,13 +1,32 @@ +import clsx from 'clsx'; import React from 'react'; +import { spring } from 'react-motion'; + +import Motion from 'soapbox/features/ui/util/optional-motion'; interface IProgressBar { - progress: number, + /** Number between 0 and 1 to represent the percentage complete. */ + progress: number + /** Height of the progress bar. */ + size?: 'sm' | 'md' } /** A horizontal meter filled to the given percentage. */ -const ProgressBar: React.FC = ({ progress }) => ( -
-
+const ProgressBar: React.FC = ({ progress, size = 'md' }) => ( +
+ + {({ width }) => ( +
+ )} +
); diff --git a/app/soapbox/components/ui/textarea/textarea.tsx b/app/soapbox/components/ui/textarea/textarea.tsx index 4b2274a21..1b1081ce4 100644 --- a/app/soapbox/components/ui/textarea/textarea.tsx +++ b/app/soapbox/components/ui/textarea/textarea.tsx @@ -26,6 +26,8 @@ interface ITextarea extends Pick) => { const [rows, setRows] = useState(autoGrow ? 1 : 4); @@ -72,9 +75,10 @@ const Textarea = React.forwardRef(({ ref={ref} rows={rows} onChange={handleChange} - className={clsx({ - 'bg-white dark:bg-transparent shadow-sm block w-full sm:text-sm rounded-md text-gray-900 dark:text-gray-100 placeholder:text-gray-600 dark:placeholder:text-gray-600 border-gray-400 dark:border-gray-800 dark:ring-1 dark:ring-gray-800 focus:ring-primary-500 focus:border-primary-500 dark:focus:ring-primary-500 dark:focus:border-primary-500': - true, + className={clsx('block w-full rounded-md text-gray-900 placeholder:text-gray-600 dark:text-gray-100 dark:placeholder:text-gray-600 sm:text-sm', { + 'bg-white dark:bg-transparent shadow-sm border-gray-400 dark:border-gray-800 dark:ring-1 dark:ring-gray-800 focus:ring-primary-500 focus:border-primary-500 dark:focus:ring-primary-500 dark:focus:border-primary-500': + theme === 'default', + 'bg-transparent border-0 focus:border-0 focus:ring-0': theme === 'transparent', 'font-mono': isCodeEditor, 'text-red-600 border-red-600': hasError, 'resize-none': !isResizeable, diff --git a/app/soapbox/components/upload-progress.tsx b/app/soapbox/components/upload-progress.tsx index 5cbb4e1b3..f434c4673 100644 --- a/app/soapbox/components/upload-progress.tsx +++ b/app/soapbox/components/upload-progress.tsx @@ -1,13 +1,11 @@ import React from 'react'; import { FormattedMessage } from 'react-intl'; -import { spring } from 'react-motion'; -import { HStack, Icon, Stack, Text } from 'soapbox/components/ui'; -import Motion from 'soapbox/features/ui/util/optional-motion'; +import { HStack, Icon, ProgressBar, Stack, Text } from 'soapbox/components/ui'; interface IUploadProgress { - /** Number between 0 and 1 to represent the percentage complete. */ - progress: number, + /** Number between 0 and 100 to represent the percentage complete. */ + progress: number } /** Displays a progress bar for uploading files. */ @@ -24,16 +22,7 @@ const UploadProgress: React.FC = ({ progress }) => { -
- - {({ width }) => - (
) - } - -
+ ); diff --git a/app/soapbox/features/chats/components/chat-composer.tsx b/app/soapbox/features/chats/components/chat-composer.tsx index a703d772f..165e7ce9d 100644 --- a/app/soapbox/features/chats/components/chat-composer.tsx +++ b/app/soapbox/features/chats/components/chat-composer.tsx @@ -3,13 +3,16 @@ import { defineMessages, IntlShape, useIntl } from 'react-intl'; import { unblockAccount } from 'soapbox/actions/accounts'; import { openModal } from 'soapbox/actions/modals'; -import { Button, Combobox, ComboboxInput, ComboboxList, ComboboxOption, ComboboxPopover, HStack, IconButton, Stack, Text, Textarea } from 'soapbox/components/ui'; +import { Button, Combobox, ComboboxInput, ComboboxList, ComboboxOption, ComboboxPopover, HStack, IconButton, Stack, Text } from 'soapbox/components/ui'; import { useChatContext } from 'soapbox/contexts/chat-context'; import UploadButton from 'soapbox/features/compose/components/upload-button'; import { search as emojiSearch } from 'soapbox/features/emoji/emoji-mart-search-light'; import { useAppDispatch, useAppSelector, useFeatures } from 'soapbox/hooks'; +import { Attachment } from 'soapbox/types/entities'; import { textAtCursorMatchesToken } from 'soapbox/utils/suggestions'; +import ChatTextarea from './chat-textarea'; + const messages = defineMessages({ placeholder: { id: 'chat.input.placeholder', defaultMessage: 'Type a message' }, send: { id: 'chat.actions.send', defaultMessage: 'Send' }, @@ -39,7 +42,10 @@ interface IChatComposer extends Pick void resetFileKey: number | null - hasAttachment?: boolean + attachments?: Attachment[] + onDeleteAttachment?: () => void + isUploading?: boolean + uploadProgress?: number } /** Textarea input for chats. */ @@ -53,7 +59,10 @@ const ChatComposer = React.forwardRef onSelectFile, resetFileKey, onPaste, - hasAttachment, + attachments = [], + onDeleteAttachment, + isUploading, + uploadProgress, }, ref) => { const intl = useIntl(); const dispatch = useAppDispatch(); @@ -68,6 +77,7 @@ const ChatComposer = React.forwardRef const [suggestions, setSuggestions] = useState(initialSuggestionState); const isSuggestionsAvailable = suggestions.list.length > 0; + const hasAttachment = attachments.length > 0; const isOverCharacterLimit = maxCharacterCount && value?.length > maxCharacterCount; const isSubmitDisabled = disabled || isOverCharacterLimit || (value.length === 0 && !hasAttachment); @@ -167,12 +177,9 @@ const ChatComposer = React.forwardRef )} - + autoGrow maxRows={5} disabled={disabled} + attachments={attachments} + onDeleteAttachment={onDeleteAttachment} + isUploading={isUploading} + uploadProgress={uploadProgress} /> {isSuggestionsAvailable ? ( diff --git a/app/soapbox/features/chats/components/chat-message-list.tsx b/app/soapbox/features/chats/components/chat-message-list.tsx index 1a2b1bd65..b1d9a0491 100644 --- a/app/soapbox/features/chats/components/chat-message-list.tsx +++ b/app/soapbox/features/chats/components/chat-message-list.tsx @@ -195,13 +195,12 @@ const ChatMessageList: React.FC = ({ chat }) => { }; const maybeRenderMedia = (chatMessage: ChatMessageEntity) => { - const { attachment } = chatMessage; - if (!attachment) return null; + if (!chatMessage.media_attachments.size) return null; return ( {(Component: any) => ( @@ -316,7 +315,7 @@ const ChatMessageList: React.FC = ({ chat }) => { space={0.5} className={clsx({ 'max-w-[85%]': true, - 'flex-1': chatMessage.attachment, + 'flex-1': !!chatMessage.media_attachments.size, 'order-2': isMyMessage, 'order-1': !isMyMessage, })} @@ -331,8 +330,8 @@ const ChatMessageList: React.FC = ({ chat }) => { className={ clsx({ 'text-ellipsis break-words relative rounded-md py-2 px-3 max-w-full space-y-2 [&_.mention]:underline': true, - 'rounded-tr-sm': chatMessage.attachment && isMyMessage, - 'rounded-tl-sm': chatMessage.attachment && !isMyMessage, + 'rounded-tr-sm': (!!chatMessage.media_attachments.size) && isMyMessage, + 'rounded-tl-sm': (!!chatMessage.media_attachments.size) && !isMyMessage, '[&_.mention]:text-primary-600 dark:[&_.mention]:text-accent-blue': !isMyMessage, '[&_.mention]:text-white dark:[&_.mention]:white': isMyMessage, 'bg-primary-500 text-white': isMyMessage, diff --git a/app/soapbox/features/chats/components/chat-pending-upload.tsx b/app/soapbox/features/chats/components/chat-pending-upload.tsx new file mode 100644 index 000000000..373d548ce --- /dev/null +++ b/app/soapbox/features/chats/components/chat-pending-upload.tsx @@ -0,0 +1,18 @@ +import React from 'react'; + +import { ProgressBar } from 'soapbox/components/ui'; + +interface IChatPendingUpload { + progress: number +} + +/** Displays a loading thumbnail for an upload in the chat composer. */ +const ChatPendingUpload: React.FC = ({ progress }) => { + return ( +
+ +
+ ); +}; + +export default ChatPendingUpload; \ No newline at end of file diff --git a/app/soapbox/features/chats/components/chat-textarea.tsx b/app/soapbox/features/chats/components/chat-textarea.tsx new file mode 100644 index 000000000..22b0877e9 --- /dev/null +++ b/app/soapbox/features/chats/components/chat-textarea.tsx @@ -0,0 +1,58 @@ +import React from 'react'; + +import { Textarea } from 'soapbox/components/ui'; +import { Attachment } from 'soapbox/types/entities'; + +import ChatPendingUpload from './chat-pending-upload'; +import ChatUpload from './chat-upload'; + +interface IChatTextarea extends React.ComponentProps { + attachments?: Attachment[] + onDeleteAttachment?: () => void + isUploading?: boolean + uploadProgress?: number +} + +/** Custom textarea for chats. */ +const ChatTextarea: React.FC = ({ + attachments, + onDeleteAttachment, + isUploading = false, + uploadProgress = 0, + ...rest +}) => { + return ( +
+ {(!!attachments?.length || isUploading) && ( +
+ {isUploading && ( + + )} + + {attachments?.map(attachment => ( + + ))} +
+ )} + +