@planningcenter/chat-react-native 1.7.0-rc.0 → 2.0.0-rc.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (56) hide show
  1. package/build/components/conversation/message.d.ts +9 -0
  2. package/build/components/conversation/message.d.ts.map +1 -0
  3. package/build/components/conversation/message.js +73 -0
  4. package/build/components/conversation/message.js.map +1 -0
  5. package/build/components/conversation/message_form.d.ts +20 -0
  6. package/build/components/conversation/message_form.d.ts.map +1 -0
  7. package/build/components/conversation/message_form.js +103 -0
  8. package/build/components/conversation/message_form.js.map +1 -0
  9. package/build/components/conversation/message_reaction.d.ts +23 -0
  10. package/build/components/conversation/message_reaction.d.ts.map +1 -1
  11. package/build/components/conversation/message_reaction.js +1 -4
  12. package/build/components/conversation/message_reaction.js.map +1 -1
  13. package/build/components/display/keyboard_view.d.ts +4 -0
  14. package/build/components/display/keyboard_view.d.ts.map +1 -0
  15. package/build/components/display/keyboard_view.js +46 -0
  16. package/build/components/display/keyboard_view.js.map +1 -0
  17. package/build/index.d.ts +4 -6
  18. package/build/index.d.ts.map +1 -1
  19. package/build/index.js +4 -6
  20. package/build/index.js.map +1 -1
  21. package/build/navigation/index.d.ts.map +1 -1
  22. package/build/screens/conversation_screen.d.ts.map +1 -1
  23. package/build/screens/conversation_screen.js +21 -84
  24. package/build/screens/conversation_screen.js.map +1 -1
  25. package/build/screens/message_actions_screen.d.ts.map +1 -1
  26. package/build/screens/message_actions_screen.js +69 -50
  27. package/build/screens/message_actions_screen.js.map +1 -1
  28. package/build/utils/index.d.ts +1 -0
  29. package/build/utils/index.d.ts.map +1 -1
  30. package/build/utils/index.js +1 -0
  31. package/build/utils/index.js.map +1 -1
  32. package/build/utils/native_adapters/clipboard.d.ts +6 -0
  33. package/build/utils/native_adapters/clipboard.d.ts.map +1 -0
  34. package/build/utils/native_adapters/clipboard.js +9 -0
  35. package/build/utils/native_adapters/clipboard.js.map +1 -0
  36. package/build/utils/native_adapters/configuration.d.ts +10 -0
  37. package/build/utils/native_adapters/configuration.d.ts.map +1 -0
  38. package/build/utils/native_adapters/configuration.js +18 -0
  39. package/build/utils/native_adapters/configuration.js.map +1 -0
  40. package/build/utils/native_adapters/index.d.ts +3 -0
  41. package/build/utils/native_adapters/index.d.ts.map +1 -0
  42. package/build/utils/native_adapters/index.js +3 -0
  43. package/build/utils/native_adapters/index.js.map +1 -0
  44. package/package.json +2 -2
  45. package/src/__tests__/utils/native_adapters/configuration.ts +21 -0
  46. package/src/components/conversation/message.tsx +87 -0
  47. package/src/components/conversation/message_form.tsx +159 -0
  48. package/src/components/conversation/message_reaction.tsx +1 -4
  49. package/src/components/display/keyboard_view.tsx +69 -0
  50. package/src/index.tsx +10 -6
  51. package/src/screens/conversation_screen.tsx +30 -115
  52. package/src/screens/message_actions_screen.tsx +114 -77
  53. package/src/utils/index.ts +1 -0
  54. package/src/utils/native_adapters/clipboard.ts +9 -0
  55. package/src/utils/native_adapters/configuration.ts +25 -0
  56. package/src/utils/native_adapters/index.ts +2 -0
@@ -0,0 +1,69 @@
1
+ import { useTheme as useNavigationTheme } from '@react-navigation/native'
2
+ import React, { useMemo, useState } from 'react'
3
+ import {
4
+ KeyboardAvoidingView,
5
+ Platform,
6
+ StyleSheet,
7
+ useWindowDimensions,
8
+ View,
9
+ ViewProps,
10
+ } from 'react-native'
11
+ import { useSafeAreaInsets } from 'react-native-safe-area-context'
12
+
13
+ export function KeyboardView({ children }: ViewProps) {
14
+ const styles = useStyles()
15
+
16
+ const { height } = useWindowDimensions()
17
+ const insets = useSafeAreaInsets()
18
+ const [pageSheetGap, setPageSheetGap] = useState<number>(0)
19
+ const keyboardVerticalOffset = useMemo(
20
+ () => pageSheetGap + insets.top - insets.bottom,
21
+ [pageSheetGap, insets.top, insets.bottom]
22
+ )
23
+
24
+ return (
25
+ <View
26
+ style={styles.pageGapView}
27
+ onLayout={event => {
28
+ const { height: viewHeight } = event.nativeEvent.layout
29
+ setPageSheetGap(height - viewHeight - insets.top)
30
+ }}
31
+ >
32
+ <KeyboardAvoidingView
33
+ keyboardVerticalOffset={keyboardVerticalOffset}
34
+ behavior={Platform.select({ ios: 'padding' })}
35
+ style={styles.container}
36
+ >
37
+ {children}
38
+ </KeyboardAvoidingView>
39
+ </View>
40
+ )
41
+ }
42
+
43
+ const useStyles = () => {
44
+ const navigationTheme = useNavigationTheme()
45
+
46
+ return StyleSheet.create({
47
+ container: {
48
+ flex: 1,
49
+ justifyContent: 'center',
50
+ },
51
+ pageGapView: {
52
+ flex: 1,
53
+ },
54
+ keyboardView: {
55
+ flex: 1,
56
+ },
57
+ listContainer: {
58
+ gap: 12,
59
+ paddingHorizontal: 16,
60
+ paddingVertical: 12,
61
+ },
62
+ textInputContainer: {
63
+ borderTopWidth: 1,
64
+ padding: 12,
65
+ backgroundColor: navigationTheme.colors.card,
66
+ },
67
+ textInput: { borderRadius: 16, borderWidth: 1, padding: 12 },
68
+ })
69
+ }
package/src/index.tsx CHANGED
@@ -1,6 +1,10 @@
1
- export * from './components'
2
- export * from './contexts'
3
- export * from './hooks'
4
- export * from './screens'
5
- export * from './utils'
6
- export * from './navigation'
1
+ export { ChatProvider, ChatContext, CreateChatThemeProps } from './contexts'
2
+ export { DesignSystemScreen } from './screens'
3
+ export { ChatStack } from './navigation'
4
+ export {
5
+ TemporaryDefaultColorsType,
6
+ ChatAdapters,
7
+ ClipboardAdapter,
8
+ Session,
9
+ Client,
10
+ } from './utils'
@@ -1,19 +1,17 @@
1
- import { PlatformPressable } from '@react-navigation/elements'
2
1
  import {
3
2
  StaticScreenProps,
4
3
  useNavigation,
5
4
  useTheme as useNavigationTheme,
6
5
  } from '@react-navigation/native'
7
- import colorFunction from 'color'
8
6
  import React, { useEffect } from 'react'
9
- import { FlatList, StyleSheet, TextInput, View } from 'react-native'
10
- import { SafeAreaView } from 'react-native-safe-area-context'
11
- import { MessageReaction } from '../components/conversation/message_reaction'
12
- import { Avatar, Text } from '../components/display'
13
- import { useTheme } from '../hooks'
7
+ import { FlatList, StyleSheet, View } from 'react-native'
8
+ import { useSafeAreaInsets } from 'react-native-safe-area-context'
9
+ import { Message } from '../components/conversation/message'
10
+ import { MessageForm } from '../components/conversation/message_form'
11
+ import { KeyboardView } from '../components/display/keyboard_view'
14
12
  import { useConversationMessages } from '../hooks/use_conversation_messages'
15
13
  import { useSuspenseGet } from '../hooks/use_suspense_api'
16
- import { ConversationResource, MessageResource } from '../types'
14
+ import { ConversationResource } from '../types'
17
15
 
18
16
  export type ConversationScreenProps = StaticScreenProps<{
19
17
  conversation_id: string
@@ -43,130 +41,47 @@ export function ConversationScreen({ route }: ConversationScreenProps) {
43
41
  }, [conversation, conversation_id, navigation])
44
42
 
45
43
  return (
46
- <SafeAreaView style={styles.container} edges={['top', 'bottom', 'left', 'right']}>
47
- <FlatList
48
- inverted
49
- contentContainerStyle={styles.listContainer}
50
- refreshing={isRefetching}
51
- onRefresh={refetch}
52
- data={messages}
53
- keyExtractor={item => item.id}
54
- renderItem={({ item }) => <Message {...item} conversation_id={conversation_id} />}
55
- onEndReached={() => fetchNextPage()}
56
- />
57
- <View style={styles.textInputContainer}>
58
- <TextInput
59
- aria-disabled={true}
60
- placeholder="Send a message"
61
- onChangeText={() => console.log('TODO: Implement sending a message')}
62
- value=""
63
- style={styles.textInput}
44
+ <View style={styles.container}>
45
+ <KeyboardView style={styles.keyboardView}>
46
+ <FlatList
47
+ inverted
48
+ contentContainerStyle={styles.listContainer}
49
+ refreshing={isRefetching}
50
+ onRefresh={refetch}
51
+ data={messages}
52
+ keyExtractor={item => item.id}
53
+ renderItem={({ item }) => <Message {...item} conversation_id={conversation_id} />}
54
+ onEndReached={() => fetchNextPage()}
64
55
  />
65
- </View>
66
- </SafeAreaView>
56
+ <MessageForm.Root conversation={conversation}>
57
+ {/* <MessageForm.AttachmentPicker /> */}
58
+ {/* <MessageForm.Commands /> */}
59
+ <MessageForm.TextInput />
60
+ <MessageForm.SubmitButton />
61
+ </MessageForm.Root>
62
+ </KeyboardView>
63
+ </View>
67
64
  )
68
65
  }
69
66
 
70
67
  const useStyles = () => {
71
68
  const navigationTheme = useNavigationTheme()
69
+ const { bottom } = useSafeAreaInsets()
72
70
 
73
71
  return StyleSheet.create({
74
72
  container: {
75
73
  flex: 1,
76
74
  justifyContent: 'center',
77
75
  backgroundColor: navigationTheme.colors.card,
76
+ paddingBottom: bottom,
77
+ },
78
+ keyboardView: {
79
+ flex: 1,
78
80
  },
79
81
  listContainer: {
80
82
  gap: 12,
81
83
  paddingHorizontal: 16,
82
84
  paddingVertical: 12,
83
85
  },
84
- textInputContainer: {
85
- borderTopWidth: 1,
86
- padding: 12,
87
- backgroundColor: navigationTheme.colors.card,
88
- },
89
- textInput: { borderRadius: 16, borderWidth: 1, padding: 12 },
90
- })
91
- }
92
-
93
- function Message(props: MessageResource & { conversation_id: string }) {
94
- const { text, conversation_id, reactionCounts } = props
95
- const styles = useMessageStyles(props)
96
- const navigation = useNavigation()
97
- const handleMessagePress = () => {
98
- navigation.navigate('MessageActions', {
99
- message_id: props.id,
100
- conversation_id,
101
- })
102
- }
103
- // TODO: open the reaction screen to show who reacted
104
- const handleReactionPress = handleMessagePress
105
-
106
- if (!text) return null
107
-
108
- return (
109
- <View style={styles.message}>
110
- {!props.mine && (
111
- <View style={styles.messageAuthor}>
112
- <Avatar size={'md'} sourceUri={props.author.avatar} />
113
- </View>
114
- )}
115
- <View style={styles.messageContent}>
116
- {!props.mine && (
117
- <Text variant="tertiary" style={styles.authorName}>
118
- {props.author.name}
119
- </Text>
120
- )}
121
- <PlatformPressable style={styles.messageBubble} onLongPress={handleMessagePress}>
122
- <Text style={styles.messageText}>{text}</Text>
123
- </PlatformPressable>
124
- <View style={styles.messageReactions}>
125
- {reactionCounts.map(reaction => (
126
- <MessageReaction
127
- key={reaction.value}
128
- reaction={reaction}
129
- onPress={handleReactionPress}
130
- />
131
- ))}
132
- </View>
133
- </View>
134
- </View>
135
- )
136
- }
137
-
138
- const useMessageStyles = ({ mine }: MessageResource) => {
139
- const { colors } = useTheme()
140
- const activeColor = colorFunction(colors.interaction).alpha(0.2).string()
141
-
142
- return StyleSheet.create({
143
- message: {
144
- gap: 8,
145
- flexDirection: mine ? 'row-reverse' : 'row',
146
- },
147
- messageContent: {
148
- gap: 8,
149
- flexShrink: 1,
150
- },
151
- messageAuthor: {
152
- flexDirection: 'row',
153
- gap: 8,
154
- },
155
- authorName: {},
156
- authorAvatar: {},
157
- messageBubble: {
158
- backgroundColor: mine ? activeColor : colors.fillColorNeutral070,
159
- borderRadius: 12,
160
- paddingVertical: 6,
161
- paddingHorizontal: 8,
162
- },
163
- messageText: {
164
- color: colors.textColorDefaultPrimary,
165
- },
166
- messageReactions: {
167
- flexDirection: 'row',
168
- gap: 4,
169
- justifyContent: mine ? 'flex-end' : 'flex-start',
170
- },
171
86
  })
172
87
  }
@@ -1,18 +1,19 @@
1
+ import { PlatformPressable } from '@react-navigation/elements'
1
2
  import { StaticScreenProps, useNavigation } from '@react-navigation/native'
2
3
  import { NativeStackNavigationOptions } from '@react-navigation/native-stack'
3
4
  import { InfiniteData, useMutation, useQueryClient } from '@tanstack/react-query'
4
- import colorFunction from 'color'
5
- import React, { useContext } from 'react'
6
- import { StyleSheet, View } from 'react-native'
7
- import { SafeAreaView } from 'react-native-safe-area-context'
8
- import { TextButton } from '../components'
5
+ import React, { useCallback, useContext } from 'react'
6
+ import { Alert, Platform, StyleSheet, useWindowDimensions, View } from 'react-native'
7
+ import { useSafeAreaInsets } from 'react-native-safe-area-context'
8
+ import { Text, TextButton } from '../components'
9
+ import { REACTION_EMOJIS, useReactionStyles } from '../components/conversation/message_reaction'
9
10
  import { ChatContext } from '../contexts'
10
11
  import { useTheme } from '../hooks'
11
12
  import { getMessagesRequestArgs, useConversationMessages } from '../hooks/use_conversation_messages'
12
13
  import { ApiCollection, ApiResource, MessageResource } from '../types'
13
14
  import { ReactionCountResource } from '../types/resources/reaction'
14
15
  import { updateRecordInPagesData } from '../utils'
15
- import { REACTION_EMOJIS } from '../components/conversation/message_reaction'
16
+ import { Clipboard } from '../utils/native_adapters'
16
17
 
17
18
  export const ReactScreenOptions: NativeStackNavigationOptions = {
18
19
  presentation: 'formSheet',
@@ -34,39 +35,61 @@ export function MessageActionsScreen({ route }: ReactionScreenProps) {
34
35
  const queryClient = useQueryClient()
35
36
  const styles = useStyles()
36
37
 
37
- const { messages, queryKey } = useConversationMessages(
38
+ const { messages, queryKey, refetch } = useConversationMessages(
38
39
  { conversation_id },
39
40
  { refetchOnMount: false }
40
41
  )
41
42
  const message = messages.find(m => m.id === message_id)
42
- const reactionValues =
43
- message?.reactionCounts.filter(reaction => reaction.mine).map(reaction => reaction.value) || []
44
-
45
- const handleReactionPress = (value: keyof typeof REACTION_EMOJIS) => {
46
- const present = reactionValues.includes(value)
47
- const requestParams = getMessagesRequestArgs({ conversation_id })
48
-
49
- // Value has already been updated
50
- const endpoint = !present ? 'react' : 'unreact'
51
- const url = `/me/conversations/${conversation_id}/messages/${message_id}/${endpoint}`
52
- const fieldsWithValueJoined = Object.fromEntries(
53
- Object.entries(requestParams.data.fields).map(([k, v]) => [k, v.join(',')])
54
- )
55
-
56
- return client.post({
57
- url,
58
- data: {
59
- ...requestParams.data,
43
+ const myReactions = message?.reactionCounts
44
+ .filter(reaction => reaction.mine)
45
+ .map(reaction => reaction.value)
46
+
47
+ const availableReactions = Object.entries(REACTION_EMOJIS).map(([value, emoji]) => {
48
+ return {
49
+ value: value as ReactionCountResource['value'],
50
+ emoji,
51
+ mine: myReactions?.includes(value as ReactionCountResource['value']),
52
+ }
53
+ })
54
+
55
+ const handleCopyPress = (text?: string) => {
56
+ Clipboard.setStringAsync(text || '')
57
+ navigation.goBack()
58
+ }
59
+
60
+ const handleReactionPress = useCallback(
61
+ ({ value, mine }: { value: keyof typeof REACTION_EMOJIS; mine?: boolean }) => {
62
+ const requestParams = getMessagesRequestArgs({ conversation_id })
63
+
64
+ // Value has already been updated
65
+ const endpoint = !mine ? 'react' : 'unreact'
66
+ const url = `/me/conversations/${conversation_id}/messages/${message_id}/${endpoint}`
67
+ const fieldsWithValueJoined = Object.fromEntries(
68
+ Object.entries(requestParams.data.fields).map(([k, v]) => [k, v.join(',')])
69
+ )
70
+
71
+ return client.post({
72
+ url,
60
73
  data: {
61
- type: 'Message',
62
- attributes: { value: value },
74
+ ...requestParams.data,
75
+ data: {
76
+ type: 'Message',
77
+ attributes: { value: value },
78
+ },
79
+ fields: fieldsWithValueJoined,
63
80
  },
64
- fields: fieldsWithValueJoined,
65
- },
66
- })
67
- }
81
+ })
82
+ },
83
+ [client, conversation_id, message_id]
84
+ )
85
+
86
+ const deleteMessage = useCallback(() => {
87
+ const url = `/me/conversations/${conversation_id}/messages/${message_id}/`
68
88
 
69
- const { mutate, isPending } = useMutation({
89
+ return client.delete({ url })
90
+ }, [client, conversation_id, message_id])
91
+
92
+ const { mutate: handleReaction, isPending } = useMutation({
70
93
  mutationFn: handleReactionPress,
71
94
  onSuccess: (result: ApiResource<MessageResource>) => {
72
95
  const updatedMessage = result.data
@@ -80,77 +103,92 @@ export function MessageActionsScreen({ route }: ReactionScreenProps) {
80
103
  )
81
104
  navigation.goBack()
82
105
  },
106
+ onError: () => {
107
+ Alert.alert('Oops', 'We were unable to react to this message. Please try again.')
108
+ },
83
109
  })
84
110
 
85
- const myReactions = message?.reactionCounts
86
- .filter(reaction => reaction.mine)
87
- .map(reaction => reaction.value)
88
-
89
- const availableReactions = Object.entries(REACTION_EMOJIS).map(([value, emoji]) => {
90
- return {
91
- value: value as ReactionCountResource['value'],
92
- emoji,
93
- mine: myReactions?.includes(value as ReactionCountResource['value']),
94
- }
111
+ const { mutate: handleDeleteMessage } = useMutation({
112
+ mutationFn: deleteMessage,
113
+ onSuccess: () => {
114
+ refetch()
115
+ navigation.goBack()
116
+ },
117
+ onError: () => {
118
+ Alert.alert('Oops', 'We were unable to delete this message. Please try again.')
119
+ },
95
120
  })
96
121
 
97
122
  return (
98
- <SafeAreaView style={styles.container} edges={['bottom']}>
123
+ <View style={styles.container}>
99
124
  <View style={styles.reactionList}>
100
- {availableReactions.map(({ value, emoji, mine }, index) => (
101
- <TextButton
102
- key={index}
103
- onPress={() => mutate(value)}
104
- style={({ pressed }) => [
105
- styles.reaction,
106
- mine && styles.reactionActive,
107
- pressed && styles.pressed,
108
- ]}
109
- variant="plain"
110
- disabled={isPending}
111
- >
112
- {emoji}
113
- </TextButton>
125
+ {availableReactions.map((reaction, index) => (
126
+ <Reaction key={index} reaction={reaction} onPress={() => handleReaction(reaction)} />
114
127
  ))}
115
128
  </View>
116
129
  <View style={styles.actions}>
117
130
  <View style={styles.actionButton}>
118
- <TextButton onPress={() => navigation.goBack()} disabled={isPending}>
119
- Copy
120
- </TextButton>
121
- <TextButton appearance="danger" onPress={() => navigation.goBack()} disabled={isPending}>
131
+ <TextButton onPress={() => handleCopyPress(message?.text)}>Copy</TextButton>
132
+ <TextButton
133
+ appearance="danger"
134
+ onPress={() => handleDeleteMessage()}
135
+ disabled={isPending}
136
+ >
122
137
  Delete
123
138
  </TextButton>
124
139
  </View>
125
140
  </View>
126
- </SafeAreaView>
141
+ </View>
142
+ )
143
+ }
144
+
145
+ const Reaction = ({
146
+ reaction,
147
+ onPress,
148
+ }: {
149
+ reaction: { value: ReactionCountResource['value']; emoji: string; mine: boolean | undefined }
150
+ onPress: () => void
151
+ }) => {
152
+ const styles = useStyles()
153
+ const reactionStyles = useReactionStyles({ mine: reaction.mine ? 1 : 0 })
154
+
155
+ return (
156
+ <PlatformPressable
157
+ key={reaction.value}
158
+ style={[reactionStyles.reaction, styles.reaction]}
159
+ onPress={onPress}
160
+ >
161
+ <Text style={reactionStyles.reactionEmoji}>{REACTION_EMOJIS[reaction.value]}</Text>
162
+ </PlatformPressable>
127
163
  )
128
164
  }
129
165
 
130
166
  const useStyles = () => {
131
167
  const theme = useTheme()
168
+ const { height } = useWindowDimensions()
169
+ const { bottom } = useSafeAreaInsets()
170
+ const btnBorderWidth = 1
171
+ const baseSize = 44
172
+ const reactionBtnSize = Platform.select({
173
+ ios: baseSize,
174
+ android: baseSize + btnBorderWidth * 2,
175
+ })
176
+
132
177
  return StyleSheet.create({
133
178
  container: {
134
179
  justifyContent: 'flex-start',
135
180
  paddingTop: 12,
181
+ paddingBottom: bottom,
136
182
  width: '100%',
137
183
  backgroundColor: theme.colors.fillColorNeutral100Inverted,
184
+ height,
138
185
  },
139
186
  reaction: {
140
- padding: 12,
141
- borderRadius: 24,
142
- includeFontPadding: false,
143
- textAlignVertical: 'center',
144
- fontSize: 28,
145
- color: '#ffffff',
146
- backgroundColor: theme.colors.fillColorNeutral050Base,
147
- },
148
- reactionActive: {
149
- borderColor: theme.colors.interaction,
150
- backgroundColor: colorFunction(theme.colors.interaction).alpha(0.2).string(),
151
- },
152
- pressed: {
153
- opacity: 0.5,
187
+ height: reactionBtnSize,
188
+ width: reactionBtnSize,
189
+ borderWidth: btnBorderWidth,
190
+ borderRadius: 32,
191
+ justifyContent: 'center',
154
192
  },
155
193
  reactionList: {
156
194
  justifyContent: 'center',
@@ -159,7 +197,6 @@ const useStyles = () => {
159
197
  flexDirection: 'row',
160
198
  borderBottomColor: theme.colors.fillColorNeutral040,
161
199
  borderBottomWidth: 1,
162
- flex: 1,
163
200
  },
164
201
  actions: { flex: 1 },
165
202
  actionButton: { padding: 12, paddingBottom: 100, gap: 12 },
@@ -5,3 +5,4 @@ export * from './space'
5
5
  export * from './client'
6
6
  export * from './uri'
7
7
  export * from './cache'
8
+ export * from './native_adapters'
@@ -0,0 +1,9 @@
1
+ export class ClipboardAdapter {
2
+ getStringAsync: () => Promise<string>
3
+ setStringAsync: (_: string) => Promise<void>
4
+
5
+ constructor(methods: ClipboardAdapter) {
6
+ this.getStringAsync = methods.getStringAsync
7
+ this.setStringAsync = methods.setStringAsync
8
+ }
9
+ }
@@ -0,0 +1,25 @@
1
+ import { ClipboardAdapter } from './clipboard'
2
+
3
+ type ChatConfigurations = {
4
+ clipboard: ClipboardAdapter
5
+ }
6
+
7
+ export class ChatAdapters {
8
+ static configure(configurations: ChatConfigurations) {
9
+ Clipboard = configurations.clipboard
10
+ }
11
+ }
12
+
13
+ const methodMissing = () => {
14
+ console.warn('ChatAdapters.configure() must be called before using any adapters')
15
+ }
16
+
17
+ let Clipboard: ClipboardAdapter = new ClipboardAdapter({
18
+ getStringAsync: async () => {
19
+ methodMissing()
20
+ return ''
21
+ },
22
+ setStringAsync: async (_: string) => methodMissing(),
23
+ })
24
+
25
+ export { Clipboard }
@@ -0,0 +1,2 @@
1
+ export * from './clipboard'
2
+ export * from './configuration'