@planningcenter/chat-react-native 4.5.0-rc.0 → 4.5.0-rc.2
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/build/components/conversation/message.d.ts.map +1 -1
- package/build/components/conversation/message.js +193 -145
- package/build/components/conversation/message.js.map +1 -1
- package/build/components/conversation/reply_shadow_message.d.ts.map +1 -1
- package/build/components/conversation/reply_shadow_message.js +135 -107
- package/build/components/conversation/reply_shadow_message.js.map +1 -1
- package/build/components/display/filter_dropdown.d.ts +6 -1
- package/build/components/display/filter_dropdown.d.ts.map +1 -1
- package/build/components/display/filter_dropdown.js +55 -35
- package/build/components/display/filter_dropdown.js.map +1 -1
- package/build/screens/conversations/components/filter_dropdown_list_header_component.d.ts.map +1 -1
- package/build/screens/conversations/components/filter_dropdown_list_header_component.js +138 -94
- package/build/screens/conversations/components/filter_dropdown_list_header_component.js.map +1 -1
- package/build/utils/styles.d.ts +1 -0
- package/build/utils/styles.d.ts.map +1 -1
- package/build/utils/styles.js +1 -1
- package/build/utils/styles.js.map +1 -1
- package/package.json +2 -2
- package/src/components/conversation/message.tsx +10 -2
- package/src/components/conversation/reply_shadow_message.tsx +14 -3
- package/src/components/display/filter_dropdown.tsx +7 -1
- package/src/screens/conversations/components/__tests__/filter_dropdown_list_header_component.test.tsx +45 -0
- package/src/screens/conversations/components/filter_dropdown_list_header_component.tsx +38 -12
- package/src/utils/styles.ts +1 -0
|
@@ -24,11 +24,15 @@ import {
|
|
|
24
24
|
CONVERSATION_MESSAGE_LIST_PADDING_HORIZONTAL,
|
|
25
25
|
MAX_FONT_SIZE_MULTIPLIER,
|
|
26
26
|
MESSAGE_AUTHOR_AVATAR_COLUMN_WIDTH,
|
|
27
|
+
MESSAGE_BACKGROUND_OUTSET,
|
|
27
28
|
platformFontWeightMedium,
|
|
28
29
|
} from '../../utils/styles'
|
|
29
30
|
import { Avatar, Icon, IconProps, Image, Text } from '../display'
|
|
30
31
|
import { TheirReplyConnector, MyReplyConnector } from './reply_connectors'
|
|
31
32
|
|
|
33
|
+
const SPACE_BELOW_MESSAGE = 12
|
|
34
|
+
const SPACE_BELOW_PRESSABLE = SPACE_BELOW_MESSAGE - MESSAGE_BACKGROUND_OUTSET
|
|
35
|
+
|
|
32
36
|
interface ReplyShadowMessageProps extends MessageResource {
|
|
33
37
|
messageId: string
|
|
34
38
|
conversation_id: number
|
|
@@ -96,6 +100,7 @@ function ShadowMessageContent({ conversation_id, ...message }: ShadowMessageCont
|
|
|
96
100
|
|
|
97
101
|
return (
|
|
98
102
|
<Pressable
|
|
103
|
+
style={styles.pressable}
|
|
99
104
|
android_ripple={{ color: colors.androidRippleNeutral }}
|
|
100
105
|
onPress={handleNavigateToReplies}
|
|
101
106
|
onPressIn={handleMessagePressIn}
|
|
@@ -104,7 +109,8 @@ function ShadowMessageContent({ conversation_id, ...message }: ShadowMessageCont
|
|
|
104
109
|
accessibilityHint="Navigate to reply screen for this message"
|
|
105
110
|
accessibilityRole="link"
|
|
106
111
|
>
|
|
107
|
-
<Animated.View style={[
|
|
112
|
+
<Animated.View style={[StyleSheet.absoluteFill, animatedBackgroundColor]} />
|
|
113
|
+
<View style={[styles.message, { marginBottom: -SPACE_BELOW_PRESSABLE }]}>
|
|
108
114
|
{!message.mine && (
|
|
109
115
|
<View>
|
|
110
116
|
<View style={[styles.avatarWrapper, !isDeleted && styles.avatarOpacity]}>
|
|
@@ -145,7 +151,7 @@ function ShadowMessageContent({ conversation_id, ...message }: ShadowMessageCont
|
|
|
145
151
|
{message.mine && (
|
|
146
152
|
<MyReplyConnector message={message} messageBubbleHeight={messageBubbleHeight} />
|
|
147
153
|
)}
|
|
148
|
-
</
|
|
154
|
+
</View>
|
|
149
155
|
</Pressable>
|
|
150
156
|
)
|
|
151
157
|
}
|
|
@@ -282,6 +288,11 @@ const useStyles = ({ mine, imageWidth = 32, imageHeight = 32 }: StylesProps = {}
|
|
|
282
288
|
const tabletWidth = width >= 744 // Smallest iPad Mini's width
|
|
283
289
|
|
|
284
290
|
return StyleSheet.create({
|
|
291
|
+
pressable: {
|
|
292
|
+
marginTop: -MESSAGE_BACKGROUND_OUTSET,
|
|
293
|
+
paddingTop: MESSAGE_BACKGROUND_OUTSET,
|
|
294
|
+
marginBottom: SPACE_BELOW_PRESSABLE,
|
|
295
|
+
},
|
|
285
296
|
message: {
|
|
286
297
|
gap: 8,
|
|
287
298
|
flexDirection: mine ? 'row-reverse' : 'row',
|
|
@@ -290,7 +301,7 @@ const useStyles = ({ mine, imageWidth = 32, imageHeight = 32 }: StylesProps = {}
|
|
|
290
301
|
messageContent: {
|
|
291
302
|
flex: 1,
|
|
292
303
|
gap: 4,
|
|
293
|
-
marginBottom:
|
|
304
|
+
marginBottom: SPACE_BELOW_MESSAGE,
|
|
294
305
|
},
|
|
295
306
|
avatarWrapper: {
|
|
296
307
|
width: MESSAGE_AUTHOR_AVATAR_COLUMN_WIDTH,
|
|
@@ -35,6 +35,11 @@ export interface FilterDropdownProps {
|
|
|
35
35
|
onMoreFilters: () => void
|
|
36
36
|
triggerLabel: string
|
|
37
37
|
showMoreFilters?: boolean
|
|
38
|
+
/**
|
|
39
|
+
* Widest the trigger may render, in points. Hosts that measure the trigger at its intrinsic
|
|
40
|
+
* width need this for the label to truncate.
|
|
41
|
+
*/
|
|
42
|
+
maxWidth?: number
|
|
38
43
|
}
|
|
39
44
|
|
|
40
45
|
export function FilterDropdown({
|
|
@@ -44,6 +49,7 @@ export function FilterDropdown({
|
|
|
44
49
|
onMoreFilters,
|
|
45
50
|
triggerLabel,
|
|
46
51
|
showMoreFilters = true,
|
|
52
|
+
maxWidth,
|
|
47
53
|
}: FilterDropdownProps) {
|
|
48
54
|
const styles = useStyles()
|
|
49
55
|
|
|
@@ -71,7 +77,7 @@ export function FilterDropdown({
|
|
|
71
77
|
return (
|
|
72
78
|
<Menu.View actions={actions} onPressAction={handlePressAction} style={styles.container}>
|
|
73
79
|
<View
|
|
74
|
-
style={styles.trigger}
|
|
80
|
+
style={[styles.trigger, maxWidth !== undefined && { maxWidth }]}
|
|
75
81
|
accessible
|
|
76
82
|
accessibilityRole="button"
|
|
77
83
|
accessibilityHint="Opens filter options"
|
|
@@ -21,6 +21,15 @@ const mockSearchEnabled = jest.fn()
|
|
|
21
21
|
const mockCanBrowseArchived = jest.fn()
|
|
22
22
|
const mockGroupsByGraphId: Record<string, { name: string }> = {}
|
|
23
23
|
|
|
24
|
+
jest.mock('react-native', () => {
|
|
25
|
+
const actual = jest.requireActual('react-native')
|
|
26
|
+
return Object.defineProperty(actual, 'useWindowDimensions', {
|
|
27
|
+
value: jest.fn(() => ({ fontScale: 1 })),
|
|
28
|
+
configurable: true,
|
|
29
|
+
})
|
|
30
|
+
})
|
|
31
|
+
const mockUseWindowDimensions = jest.requireMock('react-native').useWindowDimensions
|
|
32
|
+
|
|
24
33
|
jest.mock('@react-navigation/native', () => ({
|
|
25
34
|
...jest.requireActual('@react-navigation/native'),
|
|
26
35
|
useNavigation: () => ({ setParams: mockSetParams, navigate: mockNavigate }),
|
|
@@ -121,10 +130,46 @@ describe('FilterDropdownListHeaderComponent', () => {
|
|
|
121
130
|
mockSetParams.mockClear()
|
|
122
131
|
mockSearchEnabled.mockReturnValue(true)
|
|
123
132
|
mockCanBrowseArchived.mockReturnValue(false)
|
|
133
|
+
mockUseWindowDimensions.mockReturnValue({ fontScale: 1 })
|
|
124
134
|
setCapabilities({ groups: true, teams: true })
|
|
125
135
|
setRouteParams({})
|
|
126
136
|
})
|
|
127
137
|
|
|
138
|
+
it('keeps the dropdown and actions on one line at the default font scale', () => {
|
|
139
|
+
mockUseWindowDimensions.mockReturnValue({ fontScale: 1 })
|
|
140
|
+
|
|
141
|
+
render(<FilterDropdownListHeaderComponent />)
|
|
142
|
+
|
|
143
|
+
expect(screen.getByTestId('conversations-filter-heading-row')).toHaveStyle({
|
|
144
|
+
flexDirection: 'row',
|
|
145
|
+
})
|
|
146
|
+
})
|
|
147
|
+
|
|
148
|
+
it('stacks the actions under the dropdown at the first font size above the default', () => {
|
|
149
|
+
mockUseWindowDimensions.mockReturnValue({ fontScale: 1.12 })
|
|
150
|
+
|
|
151
|
+
render(<FilterDropdownListHeaderComponent />)
|
|
152
|
+
|
|
153
|
+
expect(screen.getByTestId('conversations-filter-heading-row')).toHaveStyle({
|
|
154
|
+
flexDirection: 'column',
|
|
155
|
+
})
|
|
156
|
+
})
|
|
157
|
+
|
|
158
|
+
it('caps the trigger to the room beside the actions so a long label truncates', () => {
|
|
159
|
+
render(<FilterDropdownListHeaderComponent />)
|
|
160
|
+
|
|
161
|
+
fireEvent(screen.getByTestId('conversations-filter-heading-row'), 'layout', {
|
|
162
|
+
nativeEvent: { layout: { width: 360 } },
|
|
163
|
+
})
|
|
164
|
+
fireEvent(screen.getByTestId('conversations-filter-actions'), 'layout', {
|
|
165
|
+
nativeEvent: { layout: { width: 160 } },
|
|
166
|
+
})
|
|
167
|
+
|
|
168
|
+
expect(screen.getByRole('button', { name: 'All conversations' })).toHaveStyle({
|
|
169
|
+
maxWidth: 156,
|
|
170
|
+
})
|
|
171
|
+
})
|
|
172
|
+
|
|
128
173
|
it('offers every filter the person can use, then More filters', () => {
|
|
129
174
|
render(<FilterDropdownListHeaderComponent />)
|
|
130
175
|
|
|
@@ -1,13 +1,13 @@
|
|
|
1
1
|
import { RouteProp, useNavigation, useRoute } from '@react-navigation/native'
|
|
2
|
-
import React, { useCallback, useMemo } from 'react'
|
|
3
|
-
import { Alert, StyleSheet, View } from 'react-native'
|
|
2
|
+
import React, { useCallback, useMemo, useState } from 'react'
|
|
3
|
+
import { Alert, LayoutChangeEvent, StyleSheet, View } from 'react-native'
|
|
4
4
|
import { FilterDropdown, FilterDropdownOption, TextButton } from '../../../components'
|
|
5
5
|
import {
|
|
6
|
-
useAtFontScaleBreakpoint,
|
|
7
6
|
useCanBrowseArchivedConversations,
|
|
8
7
|
useCanCreateConversations,
|
|
9
8
|
useCurrentPersonCache,
|
|
10
9
|
useDirectMessagesEligible,
|
|
10
|
+
useFontScale,
|
|
11
11
|
useHasDirectMessages,
|
|
12
12
|
useNewConversationEntry,
|
|
13
13
|
useTheme,
|
|
@@ -25,6 +25,8 @@ import { ConversationsScreenProps } from '../conversations_screen'
|
|
|
25
25
|
import { ListHeaderActionButton } from './list_header_action_button'
|
|
26
26
|
|
|
27
27
|
const ROW_PADDING_HORIZONTAL = 16
|
|
28
|
+
const HEADING_ROW_GAP = 12
|
|
29
|
+
const DEFAULT_FONT_SCALE = 1
|
|
28
30
|
const ALL_CONVERSATIONS_LABEL = 'All conversations'
|
|
29
31
|
const ARCHIVED_LABEL = 'Archived'
|
|
30
32
|
|
|
@@ -205,11 +207,31 @@ export const FilterDropdownListHeaderComponent = () => {
|
|
|
205
207
|
|
|
206
208
|
const showMoreFilters = canFilterByGroups || canFilterByTeams || canBrowseArchived
|
|
207
209
|
|
|
208
|
-
const
|
|
210
|
+
const stacked = useFontScale() > DEFAULT_FONT_SCALE
|
|
211
|
+
const styles = useStyles({ stacked })
|
|
212
|
+
|
|
213
|
+
// The Android menu host measures the trigger at its intrinsic width, so flex shrinking never
|
|
214
|
+
// truncates the label. Cap it to the room left beside the action buttons instead.
|
|
215
|
+
const [rowWidth, setRowWidth] = useState<number>()
|
|
216
|
+
const [actionsWidth, setActionsWidth] = useState<number>()
|
|
217
|
+
const handleRowLayout = useCallback((event: LayoutChangeEvent) => {
|
|
218
|
+
setRowWidth(event.nativeEvent.layout.width)
|
|
219
|
+
}, [])
|
|
220
|
+
const handleActionsLayout = useCallback((event: LayoutChangeEvent) => {
|
|
221
|
+
setActionsWidth(event.nativeEvent.layout.width)
|
|
222
|
+
}, [])
|
|
223
|
+
const triggerMaxWidth =
|
|
224
|
+
!stacked && rowWidth !== undefined && actionsWidth !== undefined
|
|
225
|
+
? rowWidth - ROW_PADDING_HORIZONTAL * 2 - actionsWidth - HEADING_ROW_GAP
|
|
226
|
+
: undefined
|
|
209
227
|
|
|
210
228
|
return (
|
|
211
229
|
<View style={styles.listHeader}>
|
|
212
|
-
<View
|
|
230
|
+
<View
|
|
231
|
+
style={styles.headingRow}
|
|
232
|
+
onLayout={handleRowLayout}
|
|
233
|
+
testID="conversations-filter-heading-row"
|
|
234
|
+
>
|
|
213
235
|
<View style={styles.titleContainer}>
|
|
214
236
|
<FilterDropdown
|
|
215
237
|
options={options}
|
|
@@ -218,9 +240,14 @@ export const FilterDropdownListHeaderComponent = () => {
|
|
|
218
240
|
onMoreFilters={handleOpenConversationFilters}
|
|
219
241
|
showMoreFilters={showMoreFilters}
|
|
220
242
|
triggerLabel={triggerLabel}
|
|
243
|
+
maxWidth={triggerMaxWidth}
|
|
221
244
|
/>
|
|
222
245
|
</View>
|
|
223
|
-
<View
|
|
246
|
+
<View
|
|
247
|
+
style={styles.actionsContainer}
|
|
248
|
+
onLayout={handleActionsLayout}
|
|
249
|
+
testID="conversations-filter-actions"
|
|
250
|
+
>
|
|
224
251
|
<ListHeaderActionButton
|
|
225
252
|
name="general.cog"
|
|
226
253
|
onPress={handleChatSettings}
|
|
@@ -265,9 +292,8 @@ export const FilterDropdownListHeaderComponent = () => {
|
|
|
265
292
|
)
|
|
266
293
|
}
|
|
267
294
|
|
|
268
|
-
const useStyles = () => {
|
|
295
|
+
const useStyles = ({ stacked }: { stacked: boolean }) => {
|
|
269
296
|
const theme = useTheme()
|
|
270
|
-
const atFontScaleBreakpoint = useAtFontScaleBreakpoint()
|
|
271
297
|
|
|
272
298
|
return StyleSheet.create({
|
|
273
299
|
listHeader: {
|
|
@@ -277,11 +303,11 @@ const useStyles = () => {
|
|
|
277
303
|
gap: 8,
|
|
278
304
|
},
|
|
279
305
|
headingRow: {
|
|
280
|
-
gap:
|
|
281
|
-
flexDirection:
|
|
282
|
-
alignItems:
|
|
306
|
+
gap: HEADING_ROW_GAP,
|
|
307
|
+
flexDirection: stacked ? 'column' : 'row',
|
|
308
|
+
alignItems: stacked ? 'flex-start' : 'center',
|
|
283
309
|
justifyContent: 'space-between',
|
|
284
|
-
paddingBottom:
|
|
310
|
+
paddingBottom: stacked ? 4 : 0,
|
|
285
311
|
paddingHorizontal: ROW_PADDING_HORIZONTAL,
|
|
286
312
|
},
|
|
287
313
|
titleContainer: {
|
package/src/utils/styles.ts
CHANGED
|
@@ -6,6 +6,7 @@ export const MAX_FONT_SIZE_MULTIPLIER_LANDMARK = 1.2 // Use in headers, footers,
|
|
|
6
6
|
|
|
7
7
|
export const CONVERSATION_MESSAGE_LIST_PADDING_HORIZONTAL = 16 // TODO: move to `screens/conversation/utils/styles` when `/screens/conversation` is created
|
|
8
8
|
export const MESSAGE_AUTHOR_AVATAR_COLUMN_WIDTH = 32 // TODO: move to `screens/conversation/utils/styles` when `/screens/conversation` is created
|
|
9
|
+
export const MESSAGE_BACKGROUND_OUTSET = 2
|
|
9
10
|
|
|
10
11
|
export const platformFontWeightMedium = Platform.select({
|
|
11
12
|
ios: tokens.fontWeightMedium,
|