@helpwave/hightide-native 0.6.0 → 0.9.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.
- package/package.json +6 -8
- package/src/components/card/Card.tsx +2 -4
- package/src/components/chat/ChatAttachmentMessageBubble.tsx +152 -90
- package/src/components/chat/ChatConversationList.tsx +4 -12
- package/src/components/chat/ChatConversationRow.tsx +159 -164
- package/src/components/chat/ChatDateDivider.tsx +3 -8
- package/src/components/chat/ChatMessageBubble.tsx +52 -45
- package/src/components/chat/ChatMessageComposer.tsx +4 -12
- package/src/components/chat/ChatMessageList.tsx +3 -9
- package/src/components/chat/ChatQuickReplyChip.tsx +41 -27
- package/src/components/chat/ChatSystemLine.tsx +4 -12
- package/src/components/chat/ChatThreadHeader.tsx +136 -101
- package/src/components/layout/Divider.tsx +2 -4
- package/src/components/list/ListActionItem.tsx +57 -65
- package/src/components/list/ListItem.tsx +8 -28
- package/src/components/list/ListItemTextContent.tsx +4 -2
- package/src/components/list/ListNavigationItem.tsx +55 -63
- package/src/components/user-interaction/Button.tsx +43 -49
- package/src/components/user-interaction/Checkbox.tsx +47 -48
- package/src/components/user-interaction/IconButton.tsx +40 -42
- package/src/components/user-interaction/Input.tsx +69 -80
- package/src/components/user-interaction/MultiSelect/MultiSelect.tsx +17 -0
- package/src/components/user-interaction/MultiSelect/MultiSelectComponent.tsx +40 -0
- package/src/components/user-interaction/MultiSelect/MultiSelectContext.tsx +74 -0
- package/src/components/user-interaction/MultiSelect/MultiSelectMenu.tsx +87 -0
- package/src/components/user-interaction/MultiSelect/MultiSelectOption.tsx +106 -0
- package/src/components/user-interaction/MultiSelect/MultiSelectRoot.tsx +194 -0
- package/src/components/user-interaction/MultiSelect/MultiSelectTrigger.tsx +123 -0
- package/src/components/user-interaction/MultiSelect/index.ts +2 -0
- package/src/components/user-interaction/SearchBar.tsx +11 -17
- package/src/components/user-interaction/Select/Select.tsx +17 -0
- package/src/components/user-interaction/Select/SelectComponent.tsx +40 -0
- package/src/components/user-interaction/Select/SelectContext.tsx +73 -0
- package/src/components/user-interaction/Select/SelectMenu.tsx +87 -0
- package/src/components/user-interaction/Select/SelectOption.tsx +96 -0
- package/src/components/user-interaction/Select/SelectRoot.tsx +186 -0
- package/src/components/user-interaction/Select/SelectTrigger.tsx +92 -0
- package/src/components/user-interaction/Select/index.ts +2 -0
- package/src/components/user-interaction/Switch.tsx +46 -98
- package/src/components/user-interaction/Textarea.tsx +165 -12
- package/src/components/user-interaction/ThemedPressable.tsx +48 -49
- package/src/components/visualization-and-display/Avatar.tsx +19 -17
- package/src/components/visualization-and-display/Chip.tsx +4 -12
- package/src/components/visualization-and-display/ThemedText.tsx +12 -5
- package/src/enums/chatMessageDirection.ts +18 -0
- package/src/enums/chatMessageStatus.ts +18 -0
- package/src/enums/hightideThemeModes.ts +18 -0
- package/src/enums/index.ts +7 -0
- package/src/enums/listItemContentOrder.ts +18 -0
- package/src/enums/themedTextAppearance.ts +18 -0
- package/src/enums/useMultiSelectFirstHighlightBehavior.ts +18 -0
- package/src/enums/useSelectFirstHighlightBehavior.ts +18 -0
- package/src/global-contexts/content-theme/ContentThemeProvider.tsx +9 -5
- package/src/global-contexts/theme/ThemeProvider.tsx +4 -1
- package/src/hooks/index.ts +2 -0
- package/src/hooks/useAnimatedStyleTransition.ts +460 -0
- package/src/hooks/useMemoizedTheme.ts +23 -0
- package/src/hooks/useMultiSelect.ts +3 -1
- package/src/hooks/useSelect.ts +3 -1
- package/src/icons/HightideIconRegistry.ts +2 -0
- package/src/theme/adapters/style-adapter-utils.ts +121 -27
- package/src/theme/resolvers/avatar.ts +48 -39
- package/src/theme/resolvers/button.ts +7 -17
- package/src/theme/resolvers/chat/attachment-message-bubble.ts +5 -6
- package/src/theme/resolvers/chat/conversation-row.ts +4 -6
- package/src/theme/resolvers/chat/message-bubble.ts +1 -2
- package/src/theme/resolvers/chat/message-composer.ts +1 -2
- package/src/theme/resolvers/chat/quick-reply-chip.ts +2 -3
- package/src/theme/resolvers/chat/system-line.ts +1 -2
- package/src/theme/resolvers/chat/thread-header.ts +3 -4
- package/src/theme/resolvers/checkbox.ts +2 -2
- package/src/theme/resolvers/chip.ts +1 -2
- package/src/theme/resolvers/iconButton.ts +4 -13
- package/src/theme/resolvers/input.ts +30 -14
- package/src/theme/resolvers/listItem.ts +2 -3
- package/src/theme/resolvers/multiSelect.ts +1 -2
- package/src/theme/resolvers/searchBar.ts +1 -2
- package/src/theme/resolvers/select.ts +1 -2
- package/src/theme/resolvers/themedPressable.ts +4 -15
- package/src/theme/themes/hightideThemes.ts +3 -1
- package/src/theme/types/components/chat.ts +6 -21
- package/src/theme/types/components/input.ts +3 -0
- package/src/theme/types/components/textarea.ts +1 -0
- package/src/theme/types/resolver.ts +4 -27
- package/src/utils/index.ts +1 -0
- package/src/utils/pressableInteraction.ts +7 -0
- package/src/components/user-interaction/MultiSelect.tsx +0 -282
- package/src/components/user-interaction/Select.tsx +0 -242
|
@@ -1,12 +1,10 @@
|
|
|
1
1
|
import {
|
|
2
|
-
cloneElement,
|
|
3
|
-
Fragment,
|
|
4
|
-
isValidElement,
|
|
5
2
|
useMemo,
|
|
6
|
-
|
|
3
|
+
useState,
|
|
7
4
|
type ReactNode
|
|
8
5
|
} from 'react'
|
|
9
6
|
import {
|
|
7
|
+
StyleSheet,
|
|
10
8
|
View,
|
|
11
9
|
type PressableProps
|
|
12
10
|
} from 'react-native'
|
|
@@ -14,9 +12,10 @@ import { HightideIconRegistry } from '../../icons/HightideIconRegistry'
|
|
|
14
12
|
import { ThemedIcon } from '../visualization-and-display/ThemedIcon'
|
|
15
13
|
import { ThemedText } from '../visualization-and-display/ThemedText'
|
|
16
14
|
import { useTheme } from '../../global-contexts/theme/ThemeContext'
|
|
15
|
+
import { useMemoizedTheme, useMemoizedThemeFactory } from '../../hooks/useMemoizedTheme'
|
|
17
16
|
import type {
|
|
18
17
|
AvatarState,
|
|
19
|
-
|
|
18
|
+
AvatarThemeResolvers
|
|
20
19
|
} from '../../theme/types/components/avatar'
|
|
21
20
|
import type {
|
|
22
21
|
ChatConversationRowContentContainerStyle,
|
|
@@ -28,22 +27,32 @@ import type {
|
|
|
28
27
|
ChatConversationRowTimestampStyle,
|
|
29
28
|
ChatConversationRowTitleStyle,
|
|
30
29
|
PressableContainerStyle,
|
|
31
|
-
|
|
30
|
+
PressableIconStyle,
|
|
31
|
+
PressableState,
|
|
32
|
+
PressableStateLayerStyle,
|
|
33
|
+
PressableTextStyle
|
|
32
34
|
} from '../../theme/types/components/chat'
|
|
33
|
-
import type { StyleOverwrite } from '../../theme/types/resolver'
|
|
35
|
+
import type { StyleOverwrite, StyleResolverFunction } from '../../theme/types/resolver'
|
|
36
|
+
import {
|
|
37
|
+
AvatarWithStatus,
|
|
38
|
+
type AvatarWithStatusProps
|
|
39
|
+
} from '../visualization-and-display/Avatar'
|
|
40
|
+
|
|
41
|
+
import type { ChatMessageStatus } from '../../enums/chatMessageStatus'
|
|
42
|
+
import type { IconComponent } from '../../icons/types'
|
|
34
43
|
import { ThemedPressable } from '../user-interaction'
|
|
35
|
-
import type { AvatarProps } from '../visualization-and-display/Avatar'
|
|
36
44
|
|
|
37
|
-
export type
|
|
45
|
+
export type { ChatMessageStatus }
|
|
38
46
|
|
|
39
47
|
export type ChatConversationRowProps = Omit<PressableProps, 'children' | 'style'> & {
|
|
40
|
-
|
|
48
|
+
avatarProps?: AvatarWithStatusProps,
|
|
49
|
+
avatarOverride?: ReactNode,
|
|
41
50
|
title: ReactNode,
|
|
42
51
|
timestamp?: ReactNode,
|
|
43
52
|
preview?: ReactNode,
|
|
44
53
|
unreadCount?: number,
|
|
45
54
|
isSelected?: boolean,
|
|
46
|
-
|
|
55
|
+
messageStatus?: ChatMessageStatus,
|
|
47
56
|
style?: StyleOverwrite<PressableState, PressableContainerStyle>,
|
|
48
57
|
contentContainerStyle?: StyleOverwrite<ChatConversationRowState, ChatConversationRowContentContainerStyle>,
|
|
49
58
|
headerRowStyle?: StyleOverwrite<ChatConversationRowState, ChatConversationRowHeaderRowStyle>,
|
|
@@ -52,32 +61,34 @@ export type ChatConversationRowProps = Omit<PressableProps, 'children' | 'style'
|
|
|
52
61
|
timestampStyle?: StyleOverwrite<ChatConversationRowState, ChatConversationRowTimestampStyle>,
|
|
53
62
|
previewStyle?: StyleOverwrite<ChatConversationRowState, ChatConversationRowPreviewStyle>,
|
|
54
63
|
sentIndicatorStyle?: StyleOverwrite<Record<string, never>, ChatConversationRowSentIndicatorStyle>,
|
|
55
|
-
avatarStyle?: StyleOverwrite<AvatarState, AvatarStyle>,
|
|
56
64
|
}
|
|
57
65
|
|
|
58
|
-
type
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
}
|
|
64
|
-
|
|
65
|
-
type AvatarElementProps = AvatarProps & {
|
|
66
|
-
avatarStyle?: AvatarProps['avatarStyle'],
|
|
66
|
+
type ConversationRowPressableResolvers = {
|
|
67
|
+
container: StyleResolverFunction<PressableState, PressableContainerStyle>,
|
|
68
|
+
stateLayer: StyleResolverFunction<PressableState, PressableStateLayerStyle>,
|
|
69
|
+
text: StyleResolverFunction<PressableState, PressableTextStyle>,
|
|
70
|
+
icon: StyleResolverFunction<PressableState, PressableIconStyle>,
|
|
67
71
|
}
|
|
68
72
|
|
|
69
73
|
const toNumericSize = (value: unknown): number | undefined => (
|
|
70
74
|
typeof value === 'number' ? value : undefined
|
|
71
75
|
)
|
|
72
76
|
|
|
77
|
+
const resolveMessageStatusIcon = (messageStatus: ChatMessageStatus): IconComponent => (
|
|
78
|
+
messageStatus === 'sent'
|
|
79
|
+
? HightideIconRegistry.Clock
|
|
80
|
+
: HightideIconRegistry.CheckCheck
|
|
81
|
+
)
|
|
82
|
+
|
|
73
83
|
export const ChatConversationRow = ({
|
|
74
|
-
|
|
84
|
+
avatarProps,
|
|
85
|
+
avatarOverride,
|
|
75
86
|
title,
|
|
76
87
|
timestamp,
|
|
77
88
|
preview,
|
|
78
89
|
unreadCount,
|
|
79
90
|
isSelected = false,
|
|
80
|
-
|
|
91
|
+
messageStatus,
|
|
81
92
|
disabled,
|
|
82
93
|
style,
|
|
83
94
|
contentContainerStyle,
|
|
@@ -87,169 +98,153 @@ export const ChatConversationRow = ({
|
|
|
87
98
|
timestampStyle,
|
|
88
99
|
previewStyle,
|
|
89
100
|
sentIndicatorStyle,
|
|
90
|
-
avatarStyle,
|
|
91
101
|
...props
|
|
92
102
|
}: ChatConversationRowProps) => {
|
|
93
103
|
const { theme } = useTheme()
|
|
104
|
+
const [isPressed, setIsPressed] = useState(false)
|
|
94
105
|
const isUnread = (unreadCount ?? 0) > 0
|
|
95
|
-
const
|
|
96
|
-
? HightideIconRegistry.CheckCheck
|
|
97
|
-
: HightideIconRegistry.Check
|
|
106
|
+
const staticState = useMemo(() => ({}), [])
|
|
98
107
|
|
|
99
|
-
const
|
|
108
|
+
const rowState = useMemo((): ChatConversationRowState => ({
|
|
100
109
|
isUnread,
|
|
101
110
|
isSelected,
|
|
102
111
|
isDisabled: !!disabled,
|
|
103
|
-
isPressed
|
|
104
|
-
|
|
105
|
-
isFocused: !!interaction.focused,
|
|
106
|
-
isFocusVisible: !!interaction.focusVisible,
|
|
107
|
-
})
|
|
112
|
+
isPressed,
|
|
113
|
+
}), [disabled, isPressed, isSelected, isUnread])
|
|
108
114
|
|
|
109
|
-
const
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
const
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
)
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
)
|
|
122
|
-
const
|
|
123
|
-
|
|
124
|
-
|
|
115
|
+
const pressableThemeState = useMemo((): PressableState => ({
|
|
116
|
+
isPressed,
|
|
117
|
+
isDisabled: !!disabled,
|
|
118
|
+
}), [disabled, isPressed])
|
|
119
|
+
|
|
120
|
+
const pressableResolvers = useMemoizedThemeFactory<
|
|
121
|
+
ChatConversationRowState,
|
|
122
|
+
ConversationRowPressableResolvers
|
|
123
|
+
>(theme.components.chat.conversationRow.pressable, rowState)
|
|
124
|
+
|
|
125
|
+
const resolvedContainerStyle = useMemoizedTheme(pressableResolvers.container, pressableThemeState, style)
|
|
126
|
+
const resolvedStateLayerStyle = useMemoizedTheme(pressableResolvers.stateLayer, pressableThemeState)
|
|
127
|
+
const resolvedContentContainer = useMemoizedTheme(theme.components.chat.conversationRow.contentContainer, rowState, contentContainerStyle)
|
|
128
|
+
const resolvedHeaderRow = useMemoizedTheme(theme.components.chat.conversationRow.headerRow, rowState, headerRowStyle)
|
|
129
|
+
const resolvedMessageRow = useMemoizedTheme(theme.components.chat.conversationRow.messageRow, rowState, messageRowStyle)
|
|
130
|
+
const resolvedTitle = useMemoizedTheme(theme.components.chat.conversationRow.title, rowState, titleStyle)
|
|
131
|
+
const resolvedTimestamp = useMemoizedTheme(theme.components.chat.conversationRow.timestamp, rowState, timestampStyle)
|
|
132
|
+
const resolvedPreview = useMemoizedTheme(theme.components.chat.conversationRow.preview, rowState, previewStyle)
|
|
133
|
+
const unreadBadge = useMemoizedTheme(theme.components.chat.conversationRow.unreadBadge, staticState)
|
|
134
|
+
const unreadBadgeText = useMemoizedTheme(theme.components.chat.conversationRow.unreadBadgeText, staticState)
|
|
135
|
+
const resolvedSentIndicator = useMemoizedTheme(theme.components.chat.conversationRow.sentIndicator, staticState, sentIndicatorStyle)
|
|
136
|
+
const avatarTheme = useMemoizedThemeFactory<AvatarState, AvatarThemeResolvers>(
|
|
137
|
+
theme.components.chat.conversationRow.avatar,
|
|
138
|
+
staticState
|
|
125
139
|
)
|
|
126
140
|
const avatarSize = useMemo(
|
|
127
|
-
() => toNumericSize(avatarTheme.container({}).width),
|
|
128
|
-
[avatarTheme]
|
|
141
|
+
() => toNumericSize(StyleSheet.flatten(avatarTheme.container({})).width) ?? avatarProps?.size,
|
|
142
|
+
[avatarProps?.size, avatarTheme]
|
|
129
143
|
)
|
|
144
|
+
const messageStatusIcon = messageStatus === undefined
|
|
145
|
+
? undefined
|
|
146
|
+
: resolveMessageStatusIcon(messageStatus)
|
|
147
|
+
const messageStatusIconColor = messageStatus === 'read'
|
|
148
|
+
? theme.colors.primary.color
|
|
149
|
+
: resolvedSentIndicator.color
|
|
130
150
|
|
|
131
|
-
const
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
return cloneElement(avatarElement, {
|
|
139
|
-
size: avatarSize,
|
|
140
|
-
avatarStyle: (avatarState) => avatarTheme.container(
|
|
141
|
-
{
|
|
142
|
-
...avatarState,
|
|
143
|
-
size: avatarSize,
|
|
144
|
-
},
|
|
145
|
-
avatarStyle ?? avatarElement.props.avatarStyle
|
|
146
|
-
),
|
|
147
|
-
imageStyle: (avatarState) => avatarTheme.image(
|
|
148
|
-
{
|
|
149
|
-
...avatarState,
|
|
150
|
-
size: avatarSize,
|
|
151
|
-
},
|
|
152
|
-
avatarElement.props.imageStyle
|
|
153
|
-
),
|
|
154
|
-
textStyle: (avatarState) => avatarTheme.text(
|
|
155
|
-
{
|
|
156
|
-
...avatarState,
|
|
157
|
-
size: avatarSize,
|
|
158
|
-
},
|
|
159
|
-
avatarElement.props.textStyle
|
|
160
|
-
),
|
|
161
|
-
iconStyle: (avatarState) => avatarTheme.icon(
|
|
162
|
-
{
|
|
163
|
-
...avatarState,
|
|
164
|
-
size: avatarSize,
|
|
165
|
-
},
|
|
166
|
-
avatarElement.props.iconStyle
|
|
167
|
-
),
|
|
168
|
-
})
|
|
169
|
-
}, [avatar, avatarSize, avatarStyle, avatarTheme])
|
|
151
|
+
const {
|
|
152
|
+
avatarStyle,
|
|
153
|
+
imageStyle,
|
|
154
|
+
textStyle,
|
|
155
|
+
iconStyle,
|
|
156
|
+
...restAvatarProps
|
|
157
|
+
} = avatarProps ?? {}
|
|
170
158
|
|
|
171
159
|
return (
|
|
172
160
|
<ThemedPressable
|
|
173
161
|
{...props}
|
|
174
162
|
disabled={disabled}
|
|
175
|
-
style={
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
)
|
|
181
|
-
}}
|
|
182
|
-
stateLayerStyle={(pressableState) => {
|
|
183
|
-
const state = resolveState(pressableState as PressableInteraction)
|
|
184
|
-
return theme.components.chat.conversationRow.pressable(state).stateLayer(pressableState)
|
|
185
|
-
}}
|
|
186
|
-
textStyle={(pressableState) => {
|
|
187
|
-
const state = resolveState(pressableState as PressableInteraction)
|
|
188
|
-
return theme.components.chat.conversationRow.pressable(state).text(pressableState)
|
|
163
|
+
style={resolvedContainerStyle}
|
|
164
|
+
stateLayerStyle={resolvedStateLayerStyle}
|
|
165
|
+
onPressIn={(event) => {
|
|
166
|
+
setIsPressed(true)
|
|
167
|
+
props.onPressIn?.(event)
|
|
189
168
|
}}
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
169
|
+
onPressOut={(event) => {
|
|
170
|
+
setIsPressed(false)
|
|
171
|
+
props.onPressOut?.(event)
|
|
193
172
|
}}
|
|
194
173
|
>
|
|
195
|
-
{
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
174
|
+
{avatarOverride ?? (
|
|
175
|
+
<AvatarWithStatus
|
|
176
|
+
{...restAvatarProps}
|
|
177
|
+
size={avatarSize}
|
|
178
|
+
avatarStyle={(avatarState) => avatarTheme.container(
|
|
179
|
+
{
|
|
180
|
+
...avatarState,
|
|
181
|
+
size: avatarSize,
|
|
182
|
+
},
|
|
183
|
+
avatarStyle
|
|
184
|
+
)}
|
|
185
|
+
imageStyle={(avatarState) => avatarTheme.image(
|
|
186
|
+
{
|
|
187
|
+
...avatarState,
|
|
188
|
+
size: avatarSize,
|
|
189
|
+
},
|
|
190
|
+
imageStyle
|
|
191
|
+
)}
|
|
192
|
+
textStyle={(avatarState) => avatarTheme.text(
|
|
193
|
+
{
|
|
194
|
+
...avatarState,
|
|
195
|
+
size: avatarSize,
|
|
196
|
+
},
|
|
197
|
+
textStyle
|
|
198
|
+
)}
|
|
199
|
+
iconStyle={(avatarState) => avatarTheme.icon(
|
|
200
|
+
{
|
|
201
|
+
...avatarState,
|
|
202
|
+
size: avatarSize,
|
|
203
|
+
},
|
|
204
|
+
iconStyle
|
|
205
|
+
)}
|
|
206
|
+
/>
|
|
207
|
+
)}
|
|
208
|
+
<View style={resolvedContentContainer}>
|
|
209
|
+
<View style={resolvedHeaderRow}>
|
|
210
|
+
{typeof title === 'string' || typeof title === 'number' ? (
|
|
211
|
+
<ThemedText style={resolvedTitle} numberOfLines={1}>{title}</ThemedText>
|
|
212
|
+
) : (
|
|
213
|
+
title
|
|
214
|
+
)}
|
|
215
|
+
{timestamp != null && (
|
|
216
|
+
typeof timestamp === 'string' || typeof timestamp === 'number' ? (
|
|
217
|
+
<ThemedText style={resolvedTimestamp}>{timestamp}</ThemedText>
|
|
218
|
+
) : (
|
|
219
|
+
timestamp
|
|
220
|
+
)
|
|
221
|
+
)}
|
|
222
|
+
</View>
|
|
223
|
+
<View style={resolvedMessageRow}>
|
|
224
|
+
<View style={{ flexDirection: 'row', alignItems: 'center', flex: 1, gap: theme.spacing.xs }}>
|
|
225
|
+
{messageStatusIcon != null && (
|
|
226
|
+
<ThemedIcon
|
|
227
|
+
icon={messageStatusIcon}
|
|
228
|
+
size={resolvedSentIndicator.size}
|
|
229
|
+
strokeWidth={resolvedSentIndicator.strokeWidth}
|
|
230
|
+
color={messageStatusIconColor}
|
|
231
|
+
/>
|
|
232
|
+
)}
|
|
233
|
+
{preview != null && (
|
|
234
|
+
typeof preview === 'string' || typeof preview === 'number' ? (
|
|
235
|
+
<ThemedText style={[resolvedPreview, { flex: 1 }]} numberOfLines={1}>{preview}</ThemedText>
|
|
236
|
+
) : (
|
|
237
|
+
preview
|
|
238
|
+
)
|
|
239
|
+
)}
|
|
240
|
+
</View>
|
|
241
|
+
{isUnread && (
|
|
242
|
+
<View style={unreadBadge}>
|
|
243
|
+
<ThemedText style={unreadBadgeText}>{unreadCount}</ThemedText>
|
|
249
244
|
</View>
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
245
|
+
)}
|
|
246
|
+
</View>
|
|
247
|
+
</View>
|
|
253
248
|
</ThemedPressable>
|
|
254
249
|
)
|
|
255
250
|
}
|
|
@@ -10,6 +10,7 @@ import {
|
|
|
10
10
|
} from 'react-native'
|
|
11
11
|
|
|
12
12
|
import { useTheme } from '../../global-contexts/theme/ThemeContext'
|
|
13
|
+
import { useMemoizedTheme } from '../../hooks/useMemoizedTheme'
|
|
13
14
|
import { ThemedText } from '../visualization-and-display/ThemedText'
|
|
14
15
|
import type {
|
|
15
16
|
ChatDateDividerStyle,
|
|
@@ -34,14 +35,8 @@ export const ChatDateDivider = ({
|
|
|
34
35
|
const { theme } = useTheme()
|
|
35
36
|
const state = useMemo(() => ({}), [])
|
|
36
37
|
|
|
37
|
-
const resolvedDividerStyle =
|
|
38
|
-
|
|
39
|
-
[theme, state, dividerStyle]
|
|
40
|
-
)
|
|
41
|
-
const resolvedTextStyle = useMemo(
|
|
42
|
-
() => theme.components.chat.dateDivider.text(state, textStyle),
|
|
43
|
-
[theme, state, textStyle]
|
|
44
|
-
)
|
|
38
|
+
const resolvedDividerStyle = useMemoizedTheme(theme.components.chat.dateDivider.container, state, dividerStyle)
|
|
39
|
+
const resolvedTextStyle = useMemoizedTheme(theme.components.chat.dateDivider.text, state, textStyle)
|
|
45
40
|
|
|
46
41
|
return (
|
|
47
42
|
<View {...props} style={[resolvedDividerStyle, style]}>
|
|
@@ -6,10 +6,22 @@ import {
|
|
|
6
6
|
View,
|
|
7
7
|
type ViewProps
|
|
8
8
|
} from 'react-native'
|
|
9
|
+
import { DateUtils } from '@helpwave/hightide-utils/utils'
|
|
9
10
|
import { HightideIconRegistry } from '../../icons/HightideIconRegistry'
|
|
10
11
|
import { ThemedIcon } from '../visualization-and-display/ThemedIcon'
|
|
11
12
|
import { ThemedText } from '../visualization-and-display/ThemedText'
|
|
12
13
|
import { useTheme } from '../../global-contexts/theme/ThemeContext'
|
|
14
|
+
import {
|
|
15
|
+
useDateTimeFormat,
|
|
16
|
+
useLocalization
|
|
17
|
+
} from '../../global-contexts/localization/forward-exports'
|
|
18
|
+
import { useMemoizedTheme } from '../../hooks/useMemoizedTheme'
|
|
19
|
+
import type {
|
|
20
|
+
ChatMessageStatus
|
|
21
|
+
} from '../../enums/chatMessageStatus'
|
|
22
|
+
import type {
|
|
23
|
+
ChatMessageDirection
|
|
24
|
+
} from '../../enums/chatMessageDirection'
|
|
13
25
|
import type {
|
|
14
26
|
ChatMessageBubbleBodyStyle,
|
|
15
27
|
ChatMessageBubbleBodyTextStyle,
|
|
@@ -17,17 +29,17 @@ import type {
|
|
|
17
29
|
ChatMessageBubbleMetaDataContainerStyle,
|
|
18
30
|
ChatMessageBubbleMetaDataStatusContainerStyle,
|
|
19
31
|
ChatMessageBubbleMetaDataTextStyle,
|
|
20
|
-
ChatMessageBubbleState
|
|
21
|
-
ChatMessageDirection
|
|
32
|
+
ChatMessageBubbleState
|
|
22
33
|
} from '../../theme/types/components/chat'
|
|
23
34
|
import type { StyleOverwrite } from '../../theme/types/resolver'
|
|
35
|
+
import type { IconComponent } from '../../icons/types'
|
|
24
36
|
|
|
25
|
-
export type { ChatMessageDirection }
|
|
37
|
+
export type { ChatMessageDirection, ChatMessageStatus }
|
|
26
38
|
|
|
27
39
|
export type ChatMessageBubbleProps = Omit<ViewProps, 'children' | 'style'> & {
|
|
28
40
|
direction: ChatMessageDirection,
|
|
29
|
-
timestamp
|
|
30
|
-
|
|
41
|
+
timestamp?: Date,
|
|
42
|
+
status?: ChatMessageStatus,
|
|
31
43
|
children?: ReactNode,
|
|
32
44
|
style?: StyleOverwrite<ChatMessageBubbleState, ChatMessageBubbleContainerStyle>,
|
|
33
45
|
bodyStyle?: StyleOverwrite<ChatMessageBubbleState, ChatMessageBubbleBodyStyle>,
|
|
@@ -37,10 +49,17 @@ export type ChatMessageBubbleProps = Omit<ViewProps, 'children' | 'style'> & {
|
|
|
37
49
|
metaDataTextStyle?: StyleOverwrite<ChatMessageBubbleState, ChatMessageBubbleMetaDataTextStyle>,
|
|
38
50
|
}
|
|
39
51
|
|
|
52
|
+
const resolveMessageStatusIcon = (status: ChatMessageStatus): IconComponent => (
|
|
53
|
+
status === 'sending'
|
|
54
|
+
? HightideIconRegistry.Clock
|
|
55
|
+
: status === 'sent' ? HightideIconRegistry.Check
|
|
56
|
+
: HightideIconRegistry.CheckCheck
|
|
57
|
+
)
|
|
58
|
+
|
|
40
59
|
export const ChatMessageBubble = ({
|
|
41
60
|
direction,
|
|
42
61
|
timestamp,
|
|
43
|
-
|
|
62
|
+
status,
|
|
44
63
|
children,
|
|
45
64
|
style,
|
|
46
65
|
bodyStyle,
|
|
@@ -51,36 +70,31 @@ export const ChatMessageBubble = ({
|
|
|
51
70
|
...props
|
|
52
71
|
}: ChatMessageBubbleProps) => {
|
|
53
72
|
const { theme } = useTheme()
|
|
73
|
+
const { locale } = useLocalization()
|
|
74
|
+
const { is24HourFormat, timeZone } = useDateTimeFormat()
|
|
54
75
|
const state = useMemo(() => ({ direction }), [direction])
|
|
55
76
|
|
|
56
|
-
const resolvedContainerStyle =
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
)
|
|
60
|
-
const
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
)
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
)
|
|
68
|
-
const
|
|
69
|
-
() =>
|
|
70
|
-
[
|
|
71
|
-
)
|
|
72
|
-
const resolvedMetaDataStatusContainerStyle = useMemo(
|
|
73
|
-
() => theme.components.chat.messageBubble.metaDataStatusContainer(state, metaDataStatusContainerStyle),
|
|
74
|
-
[theme, state, metaDataStatusContainerStyle]
|
|
75
|
-
)
|
|
76
|
-
const resolvedMetaDataTextStyle = useMemo(
|
|
77
|
-
() => theme.components.chat.messageBubble.metaDataText(state, metaDataTextStyle),
|
|
78
|
-
[theme, state, metaDataTextStyle]
|
|
79
|
-
)
|
|
80
|
-
const resolvedMetaDataIcon = useMemo(
|
|
81
|
-
() => theme.components.chat.messageBubble.metaDataIcon(state),
|
|
82
|
-
[theme, state]
|
|
77
|
+
const resolvedContainerStyle = useMemoizedTheme(theme.components.chat.messageBubble.container, state, style)
|
|
78
|
+
const resolvedBodyStyle = useMemoizedTheme(theme.components.chat.messageBubble.body, state, bodyStyle)
|
|
79
|
+
const resolvedBodyTextStyle = useMemoizedTheme(theme.components.chat.messageBubble.bodyText, state, bodyTextStyle)
|
|
80
|
+
const resolvedMetaDataContainerStyle = useMemoizedTheme(theme.components.chat.messageBubble.metaDataContainer, state, metaDataContainerStyle)
|
|
81
|
+
const resolvedMetaDataStatusContainerStyle = useMemoizedTheme(theme.components.chat.messageBubble.metaDataStatusContainer, state, metaDataStatusContainerStyle)
|
|
82
|
+
const resolvedMetaDataTextStyle = useMemoizedTheme(theme.components.chat.messageBubble.metaDataText, state, metaDataTextStyle)
|
|
83
|
+
const resolvedMetaDataIcon = useMemoizedTheme(theme.components.chat.messageBubble.metaDataIcon, state)
|
|
84
|
+
const formattedTimestamp = useMemo(() => (
|
|
85
|
+
timestamp === undefined
|
|
86
|
+
? undefined
|
|
87
|
+
: DateUtils.formatAbsolute(timestamp, locale, 'time', { timeZone, is24HourFormat })
|
|
88
|
+
), [timestamp, locale, timeZone, is24HourFormat])
|
|
89
|
+
const messageStatusIcon = useMemo(
|
|
90
|
+
() => (status === undefined ? undefined : resolveMessageStatusIcon(status)),
|
|
91
|
+
[status]
|
|
83
92
|
)
|
|
93
|
+
const messageStatusIconColor = useMemo(() => (
|
|
94
|
+
status === 'read'
|
|
95
|
+
? theme.colors.primary.color
|
|
96
|
+
: resolvedMetaDataIcon.color
|
|
97
|
+
), [status, resolvedMetaDataIcon.color, theme.colors.primary.color])
|
|
84
98
|
|
|
85
99
|
return (
|
|
86
100
|
<View {...props} style={resolvedContainerStyle}>
|
|
@@ -92,25 +106,18 @@ export const ChatMessageBubble = ({
|
|
|
92
106
|
)}
|
|
93
107
|
</View>
|
|
94
108
|
<View style={resolvedMetaDataContainerStyle}>
|
|
95
|
-
{
|
|
109
|
+
{messageStatusIcon != null && (
|
|
96
110
|
<View style={resolvedMetaDataStatusContainerStyle}>
|
|
97
111
|
<ThemedIcon
|
|
98
|
-
icon={
|
|
112
|
+
icon={messageStatusIcon}
|
|
99
113
|
size={resolvedMetaDataIcon.size}
|
|
100
114
|
strokeWidth={resolvedMetaDataIcon.strokeWidth}
|
|
101
|
-
color={
|
|
115
|
+
color={messageStatusIconColor}
|
|
102
116
|
/>
|
|
103
|
-
{typeof readReceipt === 'string' || typeof readReceipt === 'number' ? (
|
|
104
|
-
<ThemedText style={resolvedMetaDataTextStyle}>{readReceipt}</ThemedText>
|
|
105
|
-
) : (
|
|
106
|
-
readReceipt
|
|
107
|
-
)}
|
|
108
117
|
</View>
|
|
109
118
|
)}
|
|
110
|
-
{
|
|
111
|
-
<ThemedText style={resolvedMetaDataTextStyle}>{
|
|
112
|
-
) : (
|
|
113
|
-
timestamp
|
|
119
|
+
{formattedTimestamp != null && (
|
|
120
|
+
<ThemedText style={resolvedMetaDataTextStyle}>{formattedTimestamp}</ThemedText>
|
|
114
121
|
)}
|
|
115
122
|
</View>
|
|
116
123
|
</View>
|
|
@@ -15,6 +15,7 @@ import { useControlledState } from '@helpwave/hightide-utils/hooks'
|
|
|
15
15
|
import { HightideIconRegistry } from '../../icons/HightideIconRegistry'
|
|
16
16
|
import { IconButton } from '../user-interaction/IconButton'
|
|
17
17
|
import { useTheme } from '../../global-contexts/theme/ThemeContext'
|
|
18
|
+
import { useMemoizedTheme } from '../../hooks/useMemoizedTheme'
|
|
18
19
|
import type {
|
|
19
20
|
ChatMessageComposerInputStyle,
|
|
20
21
|
ChatMessageComposerStyle
|
|
@@ -60,18 +61,9 @@ export const ChatMessageComposer = ({
|
|
|
60
61
|
})
|
|
61
62
|
const state = useMemo(() => ({}), [])
|
|
62
63
|
|
|
63
|
-
const resolvedComposerStyle =
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
)
|
|
67
|
-
const resolvedInputStyle = useMemo(
|
|
68
|
-
() => theme.components.chat.messageComposer.input(state, inputStyle),
|
|
69
|
-
[theme, state, inputStyle]
|
|
70
|
-
)
|
|
71
|
-
const placeholderColor = useMemo(
|
|
72
|
-
() => theme.components.chat.messageComposer.placeholderColor(state),
|
|
73
|
-
[theme, state]
|
|
74
|
-
)
|
|
64
|
+
const resolvedComposerStyle = useMemoizedTheme(theme.components.chat.messageComposer.container, state, composerStyle)
|
|
65
|
+
const resolvedInputStyle = useMemoizedTheme(theme.components.chat.messageComposer.input, state, inputStyle)
|
|
66
|
+
const placeholderColor = useMemoizedTheme(theme.components.chat.messageComposer.placeholderColor, state)
|
|
75
67
|
|
|
76
68
|
const send = () => {
|
|
77
69
|
const trimmed = (value ?? '').trim()
|
|
@@ -13,6 +13,7 @@ import {
|
|
|
13
13
|
} from 'react-native'
|
|
14
14
|
|
|
15
15
|
import { useTheme } from '../../global-contexts/theme/ThemeContext'
|
|
16
|
+
import { useMemoizedTheme } from '../../hooks/useMemoizedTheme'
|
|
16
17
|
import type { ChatMessageListStyle } from '../../theme/types/components/chat'
|
|
17
18
|
import type { StyleOverwrite } from '../../theme/types/resolver'
|
|
18
19
|
|
|
@@ -37,14 +38,7 @@ export const ChatMessageList = ({
|
|
|
37
38
|
const scrollRef = useRef<ScrollView>(null)
|
|
38
39
|
const state = useMemo(() => ({}), [])
|
|
39
40
|
|
|
40
|
-
const resolvedListStyle =
|
|
41
|
-
() => theme.components.chat.messageList.container(state, listStyle),
|
|
42
|
-
[theme, state, listStyle]
|
|
43
|
-
)
|
|
44
|
-
const listGap = useMemo(
|
|
45
|
-
() => (theme.components.chat.messageList.container(state) as ViewStyle).gap,
|
|
46
|
-
[theme, state]
|
|
47
|
-
)
|
|
41
|
+
const resolvedListStyle = useMemoizedTheme(theme.components.chat.messageList.container, state, listStyle)
|
|
48
42
|
|
|
49
43
|
useEffect(() => {
|
|
50
44
|
if (autoScroll) {
|
|
@@ -56,7 +50,7 @@ export const ChatMessageList = ({
|
|
|
56
50
|
<ScrollView
|
|
57
51
|
ref={scrollRef}
|
|
58
52
|
style={[resolvedListStyle, style]}
|
|
59
|
-
contentContainerStyle={[{ gap:
|
|
53
|
+
contentContainerStyle={[{ gap: resolvedListStyle.gap }, contentContainerStyle]}
|
|
60
54
|
onScroll={onScroll}
|
|
61
55
|
scrollEventThrottle={16}
|
|
62
56
|
>
|