@@ -44,6 +44,10 @@ type ComposerFieldsProps = {
4444 linkPreview : v2 . Link | null ;
4545 /** True while the link preview is being fetched. */
4646 linkPreviewLoading : boolean ;
47+ /**
48+ * Remove the link preview (X button).
49+ */
50+ onRemoveLinkPreview : ( ) => void ;
4751 /** Auto-focus the text field.**/
4852 autoFocus ?: boolean ;
4953} ;
@@ -67,6 +71,7 @@ export function ComposerFields({
6771 onRemoveAttachment,
6872 linkPreview,
6973 linkPreviewLoading,
74+ onRemoveLinkPreview,
7075 autoFocus = true ,
7176} : ComposerFieldsProps ) {
7277 const { theme } = useTheme ( ) ;
@@ -136,6 +141,8 @@ export function ComposerFields({
136141 < ComposerLinkPreview
137142 link = { linkPreview }
138143 loading = { linkPreviewLoading }
144+ disabled = { submitting }
145+ onRemove = { onRemoveLinkPreview }
139146 />
140147 { /* Quote preview */ }
141148 { ! ! quote && < ComposerPostEmbed post = { quote } intentText = "Quoting" /> }
@@ -148,39 +155,71 @@ export function ComposerFields({
148155/**
149156 * Live link preview shown while composing: a loading row until the unfurl
150157 * resolves, then the same `LinkPreviewCard` used in the feed (so the composer
151- * preview matches what the post will look like).
158+ * preview matches what the post will look like). Both states carry an X
159+ * button that removes the preview from the draft.
152160 */
153161function ComposerLinkPreview ( {
154162 link,
155163 loading,
164+ disabled,
165+ onRemove,
156166} : {
157167 link : v2 . Link | null ;
158168 loading : boolean ;
169+ disabled : boolean ;
170+ onRemove : ( ) => void ;
159171} ) {
160172 const { theme } = useTheme ( ) ;
161173
162- if ( link ) return < LinkPreviewCard link = { link } /> ;
163- if ( ! loading ) return null ;
174+ if ( ! link && ! loading ) return null ;
164175
165176 return (
166- < View
167- style = { [
168- Atoms . flex_row ,
169- Atoms . align_center ,
170- Atoms . gap_sm ,
171- Atoms . p_md ,
172- Atoms . rounded_md ,
173- Atoms . mt_md ,
174- {
175- borderWidth : 1 ,
176- borderColor : withHexOpacity ( theme . palette . neutral_500 , '30' ) ,
177- } ,
178- ] }
179- >
180- < ActivityIndicator size = "small" color = { theme . palette . neutral_500 } />
181- < Text variant = "secondary" color = "neutral_500" >
182- Loading preview…
183- </ Text >
177+ < View >
178+ { link ? (
179+ < LinkPreviewCard link = { link } />
180+ ) : (
181+ < View
182+ style = { [
183+ Atoms . flex_row ,
184+ Atoms . align_center ,
185+ Atoms . gap_sm ,
186+ Atoms . p_md ,
187+ Atoms . rounded_md ,
188+ Atoms . mt_md ,
189+ {
190+ borderWidth : 1 ,
191+ borderColor : withHexOpacity ( theme . palette . neutral_500 , '30' ) ,
192+ } ,
193+ ] }
194+ >
195+ < ActivityIndicator size = "small" color = { theme . palette . neutral_500 } />
196+ < Text variant = "secondary" color = "neutral_500" >
197+ Loading preview…
198+ </ Text >
199+ </ View >
200+ ) }
201+ < Pressable
202+ onPress = { onRemove }
203+ disabled = { disabled }
204+ accessibilityLabel = "Remove link preview"
205+ hitSlop = { 6 }
206+ style = { {
207+ position : 'absolute' ,
208+ // Below the card's own mt_md top margin (margins stay inside the
209+ // wrapper, so the card's top edge is Spacing.md down from ours).
210+ top : Spacing . md + 4 ,
211+ right : 4 ,
212+ width : 22 ,
213+ height : 22 ,
214+ borderRadius : 11 ,
215+ alignItems : 'center' ,
216+ justifyContent : 'center' ,
217+ backgroundColor : withHexOpacity ( theme . palette . black , 'b0' ) ,
218+ opacity : disabled ? 0.4 : 1 ,
219+ } }
220+ >
221+ < Icon name = "close" size = { 14 } color = "white" />
222+ </ Pressable >
184223 </ View >
185224 ) ;
186225}
0 commit comments