From 73f77edf40f98ac5ce0e62b347e9ea4caecc9576 Mon Sep 17 00:00:00 2001 From: diondiondion Date: Tue, 24 Jun 2025 16:08:48 +0200 Subject: [PATCH] [Glitch] feat: More obvious loading state when submitting a post Port 644da36336845adbc8673ae49c670776e3cf5792 to glitch-soc Signed-off-by: Claire --- .../glitch/components/button/index.tsx | 30 ++++++++++++++++--- .../glitch/components/loading_indicator.tsx | 27 ++++++++++++++--- .../compose/components/compose_form.jsx | 24 +++++++++------ .../flavours/glitch/styles/components.scss | 23 +++++++++++++- 4 files changed, 86 insertions(+), 18 deletions(-) diff --git a/app/javascript/flavours/glitch/components/button/index.tsx b/app/javascript/flavours/glitch/components/button/index.tsx index 43f5901c74..2ca7ff12bc 100644 --- a/app/javascript/flavours/glitch/components/button/index.tsx +++ b/app/javascript/flavours/glitch/components/button/index.tsx @@ -3,12 +3,15 @@ import { useCallback } from 'react'; import classNames from 'classnames'; +import { LoadingIndicator } from 'flavours/glitch/components/loading_indicator'; + interface BaseProps extends Omit, 'children'> { block?: boolean; secondary?: boolean; compact?: boolean; dangerous?: boolean; + loading?: boolean; } interface PropsChildren extends PropsWithChildren { @@ -34,6 +37,7 @@ export const Button: React.FC = ({ secondary, compact, dangerous, + loading, className, title, text, @@ -42,13 +46,18 @@ export const Button: React.FC = ({ }) => { const handleClick = useCallback>( (e) => { - if (!disabled && onClick) { + if (disabled || loading) { + e.stopPropagation(); + e.preventDefault(); + } else if (onClick) { onClick(e); } }, - [disabled, onClick], + [disabled, loading, onClick], ); + const label = text ?? children; + return ( ); }; diff --git a/app/javascript/flavours/glitch/components/loading_indicator.tsx b/app/javascript/flavours/glitch/components/loading_indicator.tsx index fcdbe80d8a..53d216a994 100644 --- a/app/javascript/flavours/glitch/components/loading_indicator.tsx +++ b/app/javascript/flavours/glitch/components/loading_indicator.tsx @@ -6,15 +6,34 @@ const messages = defineMessages({ loading: { id: 'loading_indicator.label', defaultMessage: 'Loading…' }, }); -export const LoadingIndicator: React.FC = () => { +interface LoadingIndicatorProps { + /** + * Use role='none' to opt out of the current default role 'progressbar' + * and aria attributes which we should re-visit to check if they're appropriate. + * In Firefox the aria-label is not applied, instead an implied value of `50` is + * used as the label. + */ + role?: string; +} + +export const LoadingIndicator: React.FC = ({ + role = 'progressbar', +}) => { const intl = useIntl(); + const a11yProps = + role === 'progressbar' + ? ({ + role, + 'aria-busy': true, + 'aria-live': 'polite', + } as const) + : undefined; + return (
diff --git a/app/javascript/flavours/glitch/features/compose/components/compose_form.jsx b/app/javascript/flavours/glitch/features/compose/components/compose_form.jsx index 7969e33988..96e252ad2f 100644 --- a/app/javascript/flavours/glitch/features/compose/components/compose_form.jsx +++ b/app/javascript/flavours/glitch/features/compose/components/compose_form.jsx @@ -12,9 +12,10 @@ import { length } from 'stringz'; import { missingAltTextModal } from 'flavours/glitch/initial_state'; -import AutosuggestInput from '../../../components/autosuggest_input'; -import AutosuggestTextarea from '../../../components/autosuggest_textarea'; -import { Button } from '../../../components/button'; +import AutosuggestInput from 'flavours/glitch/components/autosuggest_input'; +import AutosuggestTextarea from 'flavours/glitch/components/autosuggest_textarea'; +import { Button } from 'flavours/glitch/components/button'; +import { LoadingIndicator } from 'flavours/glitch/components/loading_indicator'; import EmojiPickerDropdown from '../containers/emoji_picker_dropdown_container'; import PollButtonContainer from '../containers/poll_button_container'; import PrivacyDropdownContainer from '../containers/privacy_dropdown_container'; @@ -242,9 +243,8 @@ class ComposeForm extends ImmutablePureComponent { }; render () { - const { intl, onPaste, autoFocus, withoutNavigation, maxChars } = this.props; + const { intl, onPaste, autoFocus, withoutNavigation, maxChars, isSubmitting } = this.props; const { highlighted } = this.state; - const disabled = this.props.isSubmitting; return (
@@ -263,7 +263,7 @@ class ComposeForm extends ImmutablePureComponent { + loading={isSubmitting} + > + {intl.formatMessage( + this.props.isEditing ? + messages.saveChanges : + (this.props.isInReply ? messages.reply : messages.publish) + )} +
diff --git a/app/javascript/flavours/glitch/styles/components.scss b/app/javascript/flavours/glitch/styles/components.scss index 4e997a446d..86a05d9b31 100644 --- a/app/javascript/flavours/glitch/styles/components.scss +++ b/app/javascript/flavours/glitch/styles/components.scss @@ -249,6 +249,21 @@ width: 100%; } + &.loading { + cursor: wait; + + .button__label-wrapper { + // Hide the label only visually, so that + // it keeps its layout and accessibility + opacity: 0; + } + + .loading-indicator { + position: absolute; + inset: 0; + } + } + .icon { width: 18px; height: 18px; @@ -4777,14 +4792,20 @@ a.status-card { .icon-button .loading-indicator { position: static; transform: none; + color: inherit; .circular-progress { - color: $primary-text-color; + color: inherit; width: 22px; height: 22px; } } +.button--compact .loading-indicator .circular-progress { + width: 17px; + height: 17px; +} + .icon-button .loading-indicator .circular-progress { color: lighten($ui-base-color, 26%); width: 12px;