Skip to content

Commit 1982f4b

Browse files
committed
feat: extract slot components separately
1 parent 700fa62 commit 1982f4b

File tree

13 files changed

+237
-177
lines changed

13 files changed

+237
-177
lines changed
Lines changed: 51 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,51 @@
1+
import React from 'react';
2+
import { StyleSheet } from 'react-native';
3+
4+
import Animated, { LinearTransition } from 'react-native-reanimated';
5+
6+
import { InputButtons } from './components/InputButtons';
7+
import { idleRecordingStateSelector } from './utils/audioRecorderSelectors';
8+
9+
import { useMessageInputContext } from '../../contexts/messageInputContext/MessageInputContext';
10+
import { useTheme } from '../../contexts/themeContext/ThemeContext';
11+
import { useStateStore } from '../../hooks/useStateStore';
12+
13+
export const MessageComposerLeadingView = () => {
14+
const {
15+
theme: {
16+
messageInput: { inputButtonsContainer },
17+
},
18+
} = useTheme();
19+
const { audioRecorderManager, messageInputFloating } = useMessageInputContext();
20+
const { isRecordingStateIdle } = useStateStore(
21+
audioRecorderManager.state,
22+
idleRecordingStateSelector,
23+
);
24+
25+
return isRecordingStateIdle ? (
26+
<Animated.View
27+
layout={LinearTransition.duration(200)}
28+
style={[
29+
styles.inputButtonsContainer,
30+
messageInputFloating ? styles.shadow : null,
31+
inputButtonsContainer,
32+
]}
33+
>
34+
<InputButtons />
35+
</Animated.View>
36+
) : null;
37+
};
38+
39+
const styles = StyleSheet.create({
40+
inputButtonsContainer: {
41+
alignSelf: 'flex-end',
42+
},
43+
shadow: {
44+
elevation: 6,
45+
46+
shadowColor: 'hsla(0, 0%, 0%, 0.24)',
47+
shadowOffset: { height: 4, width: 0 },
48+
shadowOpacity: 0.24,
49+
shadowRadius: 12,
50+
},
51+
});
Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
export const MessageComposerTrailingView = () => {
2+
return null;
3+
};

package/src/components/MessageInput/MessageInput.tsx

Lines changed: 7 additions & 169 deletions
Original file line numberDiff line numberDiff line change
@@ -14,14 +14,16 @@ import Animated, {
1414

1515
import { useSafeAreaInsets } from 'react-native-safe-area-context';
1616

17-
import { type MessageComposerState, type TextComposerState, type UserResponse } from 'stream-chat';
17+
import { type UserResponse } from 'stream-chat';
1818

19-
import { InputButtons } from './components/InputButtons';
20-
import { LinkPreviewList } from './components/LinkPreviewList';
21-
import { OutputButtons } from './components/OutputButtons';
2219
import { MicPositionProvider } from './contexts/MicPositionContext';
20+
import { MessageComposerLeadingView } from './MessageComposerLeadingView';
21+
import { MessageComposerTrailingView } from './MessageComposerTrailingView';
22+
import { MessageInputHeaderView } from './MessageInputHeaderView';
23+
import { MessageInputLeadingView } from './MessageInputLeadingView';
24+
import { MessageInputTrailingView } from './MessageInputTrailingView';
2325

24-
import { useHasLinkPreviews } from './hooks/useLinkPreviews';
26+
import { audioRecorderSelector } from './utils/audioRecorderSelectors';
2527

2628
import { ChatContextValue, useChatContext, useOwnCapabilitiesContext } from '../../contexts';
2729
import {
@@ -36,7 +38,6 @@ import {
3638
MessageComposerAPIContextValue,
3739
useMessageComposerAPIContext,
3840
} from '../../contexts/messageComposerContext/MessageComposerAPIContext';
39-
import { useHasAttachments } from '../../contexts/messageInputContext/hooks/useHasAttachments';
4041
import { useMessageComposer } from '../../contexts/messageInputContext/hooks/useMessageComposer';
4142
import {
4243
MessageInputContextValue,
@@ -60,7 +61,6 @@ import { MessageInputHeightState } from '../../state-store/message-input-height-
6061
import { primitives } from '../../theme';
6162
import { AutoCompleteInput } from '../AutoCompleteInput/AutoCompleteInput';
6263
import { CreatePoll } from '../Poll/CreatePollContent';
63-
import { GiphyBadge } from '../ui/GiphyBadge';
6464
import { SafeAreaViewWrapper } from '../UIComponents/SafeAreaViewWrapper';
6565

6666
const useStyles = () => {
@@ -199,14 +199,6 @@ type MessageInputPropsWithContext = Pick<
199199
recordingStatus?: string;
200200
};
201201

202-
const textComposerStateSelector = (state: TextComposerState) => ({
203-
command: state.command,
204-
});
205-
206-
const messageComposerStateStoreSelector = (state: MessageComposerState) => ({
207-
quotedMessage: state.quotedMessage,
208-
});
209-
210202
const messageInputHeightStoreSelector = (state: MessageInputHeightState) => ({
211203
height: state.height,
212204
});
@@ -510,154 +502,6 @@ const MessageInputWithContext = (props: MessageInputPropsWithContext) => {
510502
);
511503
};
512504

513-
/**
514-
* PRAGMA: MessageComposerLeadingView
515-
* @param state
516-
*/
517-
518-
const idleRecordingStateSelector = (state: AudioRecorderManagerState) => ({
519-
isRecordingStateIdle: state.status === 'idle',
520-
});
521-
522-
export const MessageComposerLeadingView = () => {
523-
const {
524-
theme: {
525-
messageInput: { inputButtonsContainer },
526-
},
527-
} = useTheme();
528-
const styles = useStyles();
529-
530-
const { audioRecorderManager, messageInputFloating } = useMessageInputContext();
531-
const { isRecordingStateIdle } = useStateStore(
532-
audioRecorderManager.state,
533-
idleRecordingStateSelector,
534-
);
535-
536-
return isRecordingStateIdle ? (
537-
<Animated.View
538-
layout={LinearTransition.duration(200)}
539-
style={[
540-
styles.inputButtonsContainer,
541-
messageInputFloating ? styles.shadow : null,
542-
inputButtonsContainer,
543-
]}
544-
>
545-
<InputButtons />
546-
</Animated.View>
547-
) : null;
548-
};
549-
550-
/**
551-
* PRAGMA: MessageComposerTrailingView
552-
* @param state
553-
*/
554-
555-
export const MessageComposerTrailingView = () => {
556-
return null;
557-
};
558-
559-
/**
560-
* PRAGMA: MessageInputHeaderView
561-
* @param prevProps
562-
*/
563-
564-
export const MessageInputHeaderView = () => {
565-
const {
566-
theme: {
567-
messageInput: { contentContainer },
568-
},
569-
} = useTheme();
570-
const styles = useStyles();
571-
572-
const messageComposer = useMessageComposer();
573-
const editing = !!messageComposer.editedMessage;
574-
const { clearEditingState } = useMessageComposerAPIContext();
575-
const { quotedMessage } = useStateStore(messageComposer.state, messageComposerStateStoreSelector);
576-
const hasLinkPreviews = useHasLinkPreviews();
577-
const { audioRecorderManager, AttachmentUploadPreviewList } = useMessageInputContext();
578-
const { Reply } = useMessagesContext();
579-
const { isRecordingStateIdle } = useStateStore(
580-
audioRecorderManager.state,
581-
idleRecordingStateSelector,
582-
);
583-
const hasAttachments = useHasAttachments();
584-
585-
return isRecordingStateIdle ? (
586-
<Animated.View
587-
layout={LinearTransition.duration(200)}
588-
style={[
589-
styles.contentContainer,
590-
{
591-
paddingTop:
592-
hasAttachments || quotedMessage || editing || hasLinkPreviews
593-
? primitives.spacingXs
594-
: 0,
595-
},
596-
contentContainer,
597-
]}
598-
>
599-
{editing ? (
600-
<Animated.View entering={FadeIn.duration(200)} exiting={FadeOut.duration(200)}>
601-
<Reply
602-
mode='edit'
603-
onDismiss={clearEditingState}
604-
quotedMessage={messageComposer.editedMessage}
605-
/>
606-
</Animated.View>
607-
) : null}
608-
{quotedMessage ? (
609-
<Animated.View entering={FadeIn.duration(200)} exiting={FadeOut.duration(200)}>
610-
<Reply mode='reply' />
611-
</Animated.View>
612-
) : null}
613-
<AttachmentUploadPreviewList />
614-
<LinkPreviewList />
615-
</Animated.View>
616-
) : null;
617-
};
618-
619-
/**
620-
* PRAGMA: MessageInputLeadingView
621-
* @param prevProps
622-
*/
623-
624-
export const MessageInputLeadingView = () => {
625-
const styles = useStyles();
626-
const messageComposer = useMessageComposer();
627-
const { textComposer } = messageComposer;
628-
const { command } = useStateStore(textComposer.state, textComposerStateSelector);
629-
630-
return command ? (
631-
<View style={styles.giphyContainer}>
632-
<GiphyBadge />
633-
</View>
634-
) : null;
635-
};
636-
637-
/**
638-
* MessageInputTrailingView
639-
* @param prevProps
640-
*/
641-
642-
export const MessageInputTrailingView = () => {
643-
const styles = useStyles();
644-
const {
645-
theme: {
646-
messageInput: { outputButtonsContainer },
647-
},
648-
} = useTheme();
649-
const { audioRecorderManager } = useMessageInputContext();
650-
const { micLocked, recordingStatus } = useStateStore(
651-
audioRecorderManager.state,
652-
audioRecorderSelector,
653-
);
654-
return (recordingStatus === 'idle' || recordingStatus === 'recording') && !micLocked ? (
655-
<View style={[styles.outputButtonsContainer, outputButtonsContainer]}>
656-
<OutputButtons />
657-
</View>
658-
) : null;
659-
};
660-
661505
const areEqual = (
662506
prevProps: MessageInputPropsWithContext,
663507
nextProps: MessageInputPropsWithContext,
@@ -800,12 +644,6 @@ const MemoizedMessageInput = React.memo(
800644

801645
export type MessageInputProps = Partial<MessageInputPropsWithContext>;
802646

803-
const audioRecorderSelector = (state: AudioRecorderManagerState) => ({
804-
micLocked: state.micLocked,
805-
isRecordingStateIdle: state.status === 'idle',
806-
recordingStatus: state.status,
807-
});
808-
809647
/**
810648
* UI Component for message input
811649
* It's a consumer of
Lines changed: 80 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,80 @@
1+
import React from 'react';
2+
import { StyleSheet } from 'react-native';
3+
4+
import Animated, { FadeIn, FadeOut, LinearTransition } from 'react-native-reanimated';
5+
6+
import { LinkPreviewList } from './components/LinkPreviewList';
7+
import { useHasLinkPreviews } from './hooks/useLinkPreviews';
8+
9+
import { idleRecordingStateSelector } from './utils/audioRecorderSelectors';
10+
import { messageComposerStateStoreSelector } from './utils/messageComposerSelectors';
11+
12+
import { useMessageComposerAPIContext } from '../../contexts/messageComposerContext/MessageComposerAPIContext';
13+
import { useHasAttachments } from '../../contexts/messageInputContext/hooks/useHasAttachments';
14+
import { useMessageComposer } from '../../contexts/messageInputContext/hooks/useMessageComposer';
15+
import { useMessageInputContext } from '../../contexts/messageInputContext/MessageInputContext';
16+
import { useMessagesContext } from '../../contexts/messagesContext/MessagesContext';
17+
import { useTheme } from '../../contexts/themeContext/ThemeContext';
18+
import { useStateStore } from '../../hooks/useStateStore';
19+
import { primitives } from '../../theme';
20+
21+
export const MessageInputHeaderView = () => {
22+
const {
23+
theme: {
24+
messageInput: { contentContainer },
25+
},
26+
} = useTheme();
27+
const messageComposer = useMessageComposer();
28+
const editing = !!messageComposer.editedMessage;
29+
const { clearEditingState } = useMessageComposerAPIContext();
30+
const { quotedMessage } = useStateStore(messageComposer.state, messageComposerStateStoreSelector);
31+
const hasLinkPreviews = useHasLinkPreviews();
32+
const { audioRecorderManager, AttachmentUploadPreviewList } = useMessageInputContext();
33+
const { Reply } = useMessagesContext();
34+
const { isRecordingStateIdle } = useStateStore(
35+
audioRecorderManager.state,
36+
idleRecordingStateSelector,
37+
);
38+
const hasAttachments = useHasAttachments();
39+
40+
return isRecordingStateIdle ? (
41+
<Animated.View
42+
layout={LinearTransition.duration(200)}
43+
style={[
44+
styles.contentContainer,
45+
{
46+
paddingTop:
47+
hasAttachments || quotedMessage || editing || hasLinkPreviews
48+
? primitives.spacingXs
49+
: 0,
50+
},
51+
contentContainer,
52+
]}
53+
>
54+
{editing ? (
55+
<Animated.View entering={FadeIn.duration(200)} exiting={FadeOut.duration(200)}>
56+
<Reply
57+
mode='edit'
58+
onDismiss={clearEditingState}
59+
quotedMessage={messageComposer.editedMessage}
60+
/>
61+
</Animated.View>
62+
) : null}
63+
{quotedMessage ? (
64+
<Animated.View entering={FadeIn.duration(200)} exiting={FadeOut.duration(200)}>
65+
<Reply mode='reply' />
66+
</Animated.View>
67+
) : null}
68+
<AttachmentUploadPreviewList />
69+
<LinkPreviewList />
70+
</Animated.View>
71+
) : null;
72+
};
73+
74+
const styles = StyleSheet.create({
75+
contentContainer: {
76+
gap: primitives.spacingXxs,
77+
overflow: 'hidden',
78+
paddingHorizontal: primitives.spacingXs,
79+
},
80+
});
Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,27 @@
1+
import React from 'react';
2+
import { StyleSheet, View } from 'react-native';
3+
4+
import { textComposerStateSelector } from './utils/messageComposerSelectors';
5+
6+
import { useMessageComposer } from '../../contexts/messageInputContext/hooks/useMessageComposer';
7+
import { useStateStore } from '../../hooks/useStateStore';
8+
import { primitives } from '../../theme';
9+
import { GiphyBadge } from '../ui/GiphyBadge';
10+
11+
export const MessageInputLeadingView = () => {
12+
const messageComposer = useMessageComposer();
13+
const { textComposer } = messageComposer;
14+
const { command } = useStateStore(textComposer.state, textComposerStateSelector);
15+
16+
return command ? (
17+
<View style={styles.giphyContainer}>
18+
<GiphyBadge />
19+
</View>
20+
) : null;
21+
};
22+
23+
const styles = StyleSheet.create({
24+
giphyContainer: {
25+
padding: primitives.spacingXs,
26+
},
27+
});

0 commit comments

Comments
 (0)