Add clear action to discard composer content

When pressing the Drafts button with content in the composer, the user
can choose to discard. This now properly clears the composer by
dispatching a 'clear' action that resets to an empty state.

- Add 'clear' action type to ComposerAction
- Implement clear case in composerReducer (resets to single empty post)
- Add handleClearComposer callback in Composer.tsx
- Pass onDiscard prop through ComposerTopBar to DraftsButton
- Call onDiscard before opening drafts dialog on discard

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
Samuel Newman
2026-01-13 16:30:03 +02:00
parent 08e83b6259
commit 10d80dab7b
3 changed files with 41 additions and 1 deletions
+10 -1
View File
@@ -388,6 +388,11 @@ export const ComposePost = ({
return true
}, [thread.posts])
// Clear the composer (discard current content)
const handleClearComposer = React.useCallback(() => {
composerDispatch({type: 'clear'})
}, [composerDispatch])
const insets = useSafeAreaInsets()
const viewStyles = useMemo(
() => ({
@@ -814,6 +819,7 @@ export const ComposePost = ({
onPublish={onPressPublish}
onSelectDraft={handleSelectDraft}
onSaveDraft={saveCurrentDraft}
onDiscard={handleClearComposer}
isEmpty={isComposerEmpty}>
{missingAltError && <AltTextReminder error={missingAltError} />}
<ErrorBanner
@@ -998,7 +1004,7 @@ let ComposerPost = React.memo(function ComposerPost({
a.mb_sm,
!isActive && isLastPost && a.mb_lg,
!isActive && styles.inactivePost,
isTextOnly && IS_NATIVE && a.flex_grow,
isTextOnly && isLastPost && IS_NATIVE && a.flex_grow,
]}>
<View style={[a.flex_row, IS_NATIVE && a.flex_1]}>
<UserAvatar
@@ -1104,6 +1110,7 @@ function ComposerTopBar({
onPublish,
onSelectDraft,
onSaveDraft,
onDiscard,
isEmpty,
topBarAnimatedStyle,
children,
@@ -1118,6 +1125,7 @@ function ComposerTopBar({
onPublish: () => void
onSelectDraft: (draft: StoredDraft) => void
onSaveDraft: () => Promise<void>
onDiscard: () => void
isEmpty: boolean
topBarAnimatedStyle: StyleProp<ViewStyle>
children?: React.ReactNode
@@ -1148,6 +1156,7 @@ function ComposerTopBar({
<DraftsButton
onSelectDraft={onSelectDraft}
onSaveDraft={onSaveDraft}
onDiscard={onDiscard}
isEmpty={isEmpty}
/>
{isPublishing ? (
@@ -11,10 +11,12 @@ import {DraftsListDialog} from './DraftsListDialog'
export function DraftsButton({
onSelectDraft,
onSaveDraft,
onDiscard,
isEmpty,
}: {
onSelectDraft: (draft: StoredDraft) => void
onSaveDraft: () => Promise<void>
onDiscard: () => void
isEmpty: boolean
}) {
const {_} = useLingui()
@@ -38,6 +40,7 @@ export function DraftsButton({
}
const handleDiscardAndOpen = () => {
onDiscard()
draftsDialogControl.open()
}
+28
View File
@@ -131,6 +131,9 @@ export type ComposerAction =
/** Map of localId -> loaded media path/URL */
loadedMedia: Map<string, string>
}
| {
type: 'clear'
}
export const MAX_IMAGES = 4
@@ -309,6 +312,31 @@ export function composerReducer(
},
}
}
case 'clear': {
return {
activePostIndex: 0,
mutableNeedsFocusActive: true,
draftId: undefined,
thread: {
posts: [
{
id: nanoid(),
richtext: new RichText({text: ''}),
shortenedGraphemeLength: 0,
labels: [],
embed: {
quote: undefined,
media: undefined,
link: undefined,
},
},
],
// Keep the user's default interaction settings
postgate: state.thread.postgate,
threadgate: state.thread.threadgate,
},
}
}
}
}