@planningcenter/chat-react-native 4.5.0-rc.2 → 4.5.0-rc.4
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/display/filter_dropdown.d.ts +16 -0
- package/build/components/display/filter_dropdown.d.ts.map +1 -1
- package/build/components/display/filter_dropdown.js +105 -57
- package/build/components/display/filter_dropdown.js.map +1 -1
- package/build/components/display/index.d.ts +1 -0
- package/build/components/display/index.d.ts.map +1 -1
- package/build/components/display/index.js +1 -0
- package/build/components/display/index.js.map +1 -1
- package/build/components/display/popover_menu_view.d.ts +8 -0
- package/build/components/display/popover_menu_view.d.ts.map +1 -0
- package/build/components/display/popover_menu_view.js +482 -0
- package/build/components/display/popover_menu_view.js.map +1 -0
- 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 +159 -124
- package/build/screens/conversations/components/filter_dropdown_list_header_component.js.map +1 -1
- package/build/screens/conversations/conversations_screen.d.ts.map +1 -1
- package/build/screens/conversations/conversations_screen.js +1 -2
- package/build/screens/conversations/conversations_screen.js.map +1 -1
- package/build/screens/design_system_screen.d.ts.map +1 -1
- package/build/screens/design_system_screen.js +289 -201
- package/build/screens/design_system_screen.js.map +1 -1
- package/build/utils/native_adapters/menu.d.ts +1 -2
- package/build/utils/native_adapters/menu.d.ts.map +1 -1
- package/build/utils/native_adapters/menu.js +2 -41
- package/build/utils/native_adapters/menu.js.map +1 -1
- package/package.json +2 -2
- package/src/components/display/__tests__/filter_dropdown.test.tsx +18 -2
- package/src/components/display/__tests__/popover_menu_view.test.tsx +63 -0
- package/src/components/display/filter_dropdown.tsx +53 -19
- package/src/components/display/index.ts +1 -0
- package/src/components/display/popover_menu_view.tsx +239 -0
- package/src/screens/conversations/__tests__/conversations_screen.test.tsx +2 -12
- package/src/screens/conversations/components/__tests__/filter_dropdown_list_header_component.test.tsx +0 -54
- package/src/screens/conversations/components/filter_dropdown_list_header_component.tsx +41 -27
- package/src/screens/conversations/conversations_screen.tsx +1 -2
- package/src/screens/design_system_screen.tsx +34 -1
- package/src/utils/native_adapters/menu.tsx +4 -18
- package/src/utils/native_adapters/__tests__/menu.test.tsx +0 -46
|
@@ -1,13 +1,17 @@
|
|
|
1
1
|
import { RouteProp, useNavigation, useRoute } from '@react-navigation/native'
|
|
2
2
|
import React, { useCallback, useMemo, useState } from 'react'
|
|
3
3
|
import { Alert, LayoutChangeEvent, StyleSheet, View } from 'react-native'
|
|
4
|
-
import {
|
|
4
|
+
import {
|
|
5
|
+
FilterDropdown,
|
|
6
|
+
FilterDropdownOption,
|
|
7
|
+
FilterDropdownTrigger,
|
|
8
|
+
TextButton,
|
|
9
|
+
} from '../../../components'
|
|
5
10
|
import {
|
|
6
11
|
useCanBrowseArchivedConversations,
|
|
7
12
|
useCanCreateConversations,
|
|
8
13
|
useCurrentPersonCache,
|
|
9
14
|
useDirectMessagesEligible,
|
|
10
|
-
useFontScale,
|
|
11
15
|
useHasDirectMessages,
|
|
12
16
|
useNewConversationEntry,
|
|
13
17
|
useTheme,
|
|
@@ -26,7 +30,6 @@ import { ListHeaderActionButton } from './list_header_action_button'
|
|
|
26
30
|
|
|
27
31
|
const ROW_PADDING_HORIZONTAL = 16
|
|
28
32
|
const HEADING_ROW_GAP = 12
|
|
29
|
-
const DEFAULT_FONT_SCALE = 1
|
|
30
33
|
const ALL_CONVERSATIONS_LABEL = 'All conversations'
|
|
31
34
|
const ARCHIVED_LABEL = 'Archived'
|
|
32
35
|
|
|
@@ -207,31 +210,43 @@ export const FilterDropdownListHeaderComponent = () => {
|
|
|
207
210
|
|
|
208
211
|
const showMoreFilters = canFilterByGroups || canFilterByTeams || canBrowseArchived
|
|
209
212
|
|
|
210
|
-
const
|
|
211
|
-
const styles = useStyles({ stacked })
|
|
213
|
+
const styles = useStyles()
|
|
212
214
|
|
|
213
|
-
//
|
|
214
|
-
//
|
|
215
|
+
// Cap the trigger beside the actions while the widest fixed label fits there, so only longer
|
|
216
|
+
// labels truncate; past that, cap it to the row and let the actions wrap.
|
|
215
217
|
const [rowWidth, setRowWidth] = useState<number>()
|
|
216
218
|
const [actionsWidth, setActionsWidth] = useState<number>()
|
|
219
|
+
const [widestFixedLabelWidth, setWidestFixedLabelWidth] = useState<number>()
|
|
217
220
|
const handleRowLayout = useCallback((event: LayoutChangeEvent) => {
|
|
218
221
|
setRowWidth(event.nativeEvent.layout.width)
|
|
219
222
|
}, [])
|
|
220
223
|
const handleActionsLayout = useCallback((event: LayoutChangeEvent) => {
|
|
221
224
|
setActionsWidth(event.nativeEvent.layout.width)
|
|
222
225
|
}, [])
|
|
223
|
-
const
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
226
|
+
const handleWidestFixedLabelLayout = useCallback((event: LayoutChangeEvent) => {
|
|
227
|
+
setWidestFixedLabelWidth(event.nativeEvent.layout.width)
|
|
228
|
+
}, [])
|
|
229
|
+
const triggerMaxWidth = useMemo(() => {
|
|
230
|
+
if (rowWidth === undefined) return undefined
|
|
231
|
+
|
|
232
|
+
const rowContentWidth = rowWidth - ROW_PADDING_HORIZONTAL * 2
|
|
233
|
+
if (actionsWidth === undefined || widestFixedLabelWidth === undefined) return rowContentWidth
|
|
234
|
+
|
|
235
|
+
const roomBesideActions = rowContentWidth - actionsWidth - HEADING_ROW_GAP
|
|
236
|
+
return roomBesideActions >= widestFixedLabelWidth ? roomBesideActions : rowContentWidth
|
|
237
|
+
}, [rowWidth, actionsWidth, widestFixedLabelWidth])
|
|
227
238
|
|
|
228
239
|
return (
|
|
229
240
|
<View style={styles.listHeader}>
|
|
230
|
-
<View
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
241
|
+
<View style={styles.widestFixedLabel} pointerEvents="none">
|
|
242
|
+
<FilterDropdownTrigger
|
|
243
|
+
label={ALL_CONVERSATIONS_LABEL}
|
|
244
|
+
onLayout={handleWidestFixedLabelLayout}
|
|
245
|
+
accessibilityElementsHidden
|
|
246
|
+
importantForAccessibility="no-hide-descendants"
|
|
247
|
+
/>
|
|
248
|
+
</View>
|
|
249
|
+
<View style={styles.headingRow} onLayout={handleRowLayout}>
|
|
235
250
|
<View style={styles.titleContainer}>
|
|
236
251
|
<FilterDropdown
|
|
237
252
|
options={options}
|
|
@@ -243,11 +258,7 @@ export const FilterDropdownListHeaderComponent = () => {
|
|
|
243
258
|
maxWidth={triggerMaxWidth}
|
|
244
259
|
/>
|
|
245
260
|
</View>
|
|
246
|
-
<View
|
|
247
|
-
style={styles.actionsContainer}
|
|
248
|
-
onLayout={handleActionsLayout}
|
|
249
|
-
testID="conversations-filter-actions"
|
|
250
|
-
>
|
|
261
|
+
<View style={styles.actionsContainer} onLayout={handleActionsLayout}>
|
|
251
262
|
<ListHeaderActionButton
|
|
252
263
|
name="general.cog"
|
|
253
264
|
onPress={handleChatSettings}
|
|
@@ -292,7 +303,7 @@ export const FilterDropdownListHeaderComponent = () => {
|
|
|
292
303
|
)
|
|
293
304
|
}
|
|
294
305
|
|
|
295
|
-
const useStyles = (
|
|
306
|
+
const useStyles = () => {
|
|
296
307
|
const theme = useTheme()
|
|
297
308
|
|
|
298
309
|
return StyleSheet.create({
|
|
@@ -304,15 +315,18 @@ const useStyles = ({ stacked }: { stacked: boolean }) => {
|
|
|
304
315
|
},
|
|
305
316
|
headingRow: {
|
|
306
317
|
gap: HEADING_ROW_GAP,
|
|
307
|
-
flexDirection:
|
|
308
|
-
|
|
318
|
+
flexDirection: 'row',
|
|
319
|
+
flexWrap: 'wrap',
|
|
320
|
+
alignItems: 'center',
|
|
309
321
|
justifyContent: 'space-between',
|
|
310
|
-
paddingBottom: stacked ? 4 : 0,
|
|
311
322
|
paddingHorizontal: ROW_PADDING_HORIZONTAL,
|
|
312
323
|
},
|
|
313
324
|
titleContainer: {
|
|
314
|
-
flexShrink:
|
|
315
|
-
|
|
325
|
+
flexShrink: 0,
|
|
326
|
+
},
|
|
327
|
+
widestFixedLabel: {
|
|
328
|
+
position: 'absolute',
|
|
329
|
+
opacity: 0,
|
|
316
330
|
},
|
|
317
331
|
actionsContainer: {
|
|
318
332
|
flexDirection: 'row',
|
|
@@ -8,7 +8,6 @@ import { analyticsEvents, usePublishProductAnalyticsEvent } from '../../hooks/us
|
|
|
8
8
|
import { useSearchEnabled } from '../../hooks/use_search_enabled'
|
|
9
9
|
import { AppName } from '../../types/resources/app_name'
|
|
10
10
|
import { GraphId } from '../../types/resources/group_resource'
|
|
11
|
-
import { Menu } from '../../utils/native_adapters'
|
|
12
11
|
import { FilterDropdownListHeaderComponent } from './components/filter_dropdown_list_header_component'
|
|
13
12
|
import { ListHeaderComponent } from './components/list_header_component'
|
|
14
13
|
import { SearchListHeaderComponent } from './components/search_list_header_component'
|
|
@@ -41,7 +40,7 @@ const useListHeaderComponent = () => {
|
|
|
41
40
|
const filterDropdownEnabled = useFilterDropdownEnabled()
|
|
42
41
|
const searchEnabled = useSearchEnabled()
|
|
43
42
|
|
|
44
|
-
if (filterDropdownEnabled
|
|
43
|
+
if (filterDropdownEnabled) return FilterDropdownListHeaderComponent
|
|
45
44
|
if (searchEnabled) return SearchListHeaderComponent
|
|
46
45
|
|
|
47
46
|
return ListHeaderComponent
|
|
@@ -20,6 +20,7 @@ import {
|
|
|
20
20
|
IconButton,
|
|
21
21
|
Image,
|
|
22
22
|
Person,
|
|
23
|
+
PopoverMenuView,
|
|
23
24
|
Spinner,
|
|
24
25
|
Switch,
|
|
25
26
|
Text,
|
|
@@ -223,6 +224,7 @@ function PressablesSection({ isLast }: SectionProps) {
|
|
|
223
224
|
const styles = useStyles()
|
|
224
225
|
const [filterSelected, setFilterSelected] = useState('Groups')
|
|
225
226
|
const [dropdownSelected, setDropdownSelected] = useState('all')
|
|
227
|
+
const [popoverSelected, setPopoverSelected] = useState('all')
|
|
226
228
|
|
|
227
229
|
return (
|
|
228
230
|
<CollapsableSection title="Pressables" isLast={isLast}>
|
|
@@ -400,7 +402,7 @@ function PressablesSection({ isLast }: SectionProps) {
|
|
|
400
402
|
</Group>
|
|
401
403
|
<Group
|
|
402
404
|
title="FilterDropdown"
|
|
403
|
-
description="Trigger that opens the host's native menu of filter options plus a More filters action.
|
|
405
|
+
description="Trigger that opens the host's native menu of filter options plus a More filters action. Falls back to PopoverMenuView when the host configures no menu adapter."
|
|
404
406
|
>
|
|
405
407
|
<Row style={styles.alignRowLeft}>
|
|
406
408
|
<FilterDropdown
|
|
@@ -414,6 +416,37 @@ function PressablesSection({ isLast }: SectionProps) {
|
|
|
414
416
|
/>
|
|
415
417
|
</Row>
|
|
416
418
|
</Group>
|
|
419
|
+
<Group
|
|
420
|
+
title="PopoverMenuView"
|
|
421
|
+
description="The JS-only menu FilterDropdown falls back to when the host configures no menu adapter. Opens under the trigger, or above it when the trigger is in the lower half of the screen."
|
|
422
|
+
>
|
|
423
|
+
<Row style={styles.alignRowLeft}>
|
|
424
|
+
<PopoverMenuView
|
|
425
|
+
actions={[
|
|
426
|
+
...filterDropdownOptions.map(option => ({
|
|
427
|
+
id: option.id,
|
|
428
|
+
title: option.label,
|
|
429
|
+
state: option.id === popoverSelected ? ('on' as const) : ('off' as const),
|
|
430
|
+
})),
|
|
431
|
+
{
|
|
432
|
+
id: 'more',
|
|
433
|
+
title: '',
|
|
434
|
+
displayInline: true,
|
|
435
|
+
subactions: [{ id: 'more_filters', title: 'More filters' }],
|
|
436
|
+
},
|
|
437
|
+
]}
|
|
438
|
+
onPressAction={({ nativeEvent }) =>
|
|
439
|
+
nativeEvent.event === 'more_filters'
|
|
440
|
+
? Alert.alert('More filters')
|
|
441
|
+
: setPopoverSelected(nativeEvent.event)
|
|
442
|
+
}
|
|
443
|
+
>
|
|
444
|
+
<Text>
|
|
445
|
+
{filterDropdownOptions.find(option => option.id === popoverSelected)?.label} ▾
|
|
446
|
+
</Text>
|
|
447
|
+
</PopoverMenuView>
|
|
448
|
+
</Row>
|
|
449
|
+
</Group>
|
|
417
450
|
<Group
|
|
418
451
|
title="IconButton"
|
|
419
452
|
description="Supports different appearances, sizes, along with loading & disabled states. Use `iconStyle` for custom colors and font sizes. Requires `accessibilityLabel` as icon's don't provide context to screen readers."
|
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
import React
|
|
2
|
-
import { StyleProp,
|
|
1
|
+
import React from 'react'
|
|
2
|
+
import { StyleProp, ViewStyle } from 'react-native'
|
|
3
3
|
|
|
4
4
|
// =================================
|
|
5
5
|
// ====== Types ====================
|
|
@@ -47,24 +47,10 @@ export type MenuView = React.ComponentType<MenuViewProps>
|
|
|
47
47
|
// ====== Adapter ==================
|
|
48
48
|
// =================================
|
|
49
49
|
|
|
50
|
-
function UnconfiguredMenuView({ children, style, testID }: MenuViewProps) {
|
|
51
|
-
useEffect(() => {
|
|
52
|
-
console.warn('ChatAdapters.configure() must include a menu adapter before rendering a menu')
|
|
53
|
-
}, [])
|
|
54
|
-
|
|
55
|
-
return (
|
|
56
|
-
<View style={style} testID={testID}>
|
|
57
|
-
{children}
|
|
58
|
-
</View>
|
|
59
|
-
)
|
|
60
|
-
}
|
|
61
|
-
|
|
62
50
|
export class MenuAdapter {
|
|
63
|
-
View
|
|
64
|
-
isConfigured: boolean
|
|
51
|
+
View?: MenuView
|
|
65
52
|
|
|
66
53
|
constructor(methods?: { View: MenuView }) {
|
|
67
|
-
this.View = methods?.View
|
|
68
|
-
this.isConfigured = !!methods?.View
|
|
54
|
+
this.View = methods?.View
|
|
69
55
|
}
|
|
70
56
|
}
|
|
@@ -1,46 +0,0 @@
|
|
|
1
|
-
import { render, screen } from '@testing-library/react-native'
|
|
2
|
-
import React from 'react'
|
|
3
|
-
import { Text } from 'react-native'
|
|
4
|
-
import { MenuAdapter, MenuViewProps } from '../menu'
|
|
5
|
-
|
|
6
|
-
const actions = [{ id: 'all', title: 'All' }]
|
|
7
|
-
|
|
8
|
-
describe('MenuAdapter', () => {
|
|
9
|
-
it('renders the trigger and warns when the host configured no menu', () => {
|
|
10
|
-
const warn = jest.spyOn(console, 'warn').mockImplementation(() => {})
|
|
11
|
-
const { View, isConfigured } = new MenuAdapter()
|
|
12
|
-
|
|
13
|
-
expect(isConfigured).toBe(false)
|
|
14
|
-
|
|
15
|
-
render(
|
|
16
|
-
<View actions={actions}>
|
|
17
|
-
<Text>Trigger</Text>
|
|
18
|
-
</View>
|
|
19
|
-
)
|
|
20
|
-
|
|
21
|
-
expect(screen.getByText('Trigger')).toBeTruthy()
|
|
22
|
-
expect(warn).toHaveBeenCalledTimes(1)
|
|
23
|
-
warn.mockRestore()
|
|
24
|
-
})
|
|
25
|
-
|
|
26
|
-
it('renders through the view the host supplies', () => {
|
|
27
|
-
const HostMenuView = ({ actions: hostActions, children }: MenuViewProps) => (
|
|
28
|
-
<>
|
|
29
|
-
{children}
|
|
30
|
-
<Text>{hostActions.map(action => action.title).join(',')}</Text>
|
|
31
|
-
</>
|
|
32
|
-
)
|
|
33
|
-
const { View, isConfigured } = new MenuAdapter({ View: HostMenuView })
|
|
34
|
-
|
|
35
|
-
expect(isConfigured).toBe(true)
|
|
36
|
-
|
|
37
|
-
render(
|
|
38
|
-
<View actions={actions}>
|
|
39
|
-
<Text>Trigger</Text>
|
|
40
|
-
</View>
|
|
41
|
-
)
|
|
42
|
-
|
|
43
|
-
expect(screen.getByText('Trigger')).toBeTruthy()
|
|
44
|
-
expect(screen.getByText('All')).toBeTruthy()
|
|
45
|
-
})
|
|
46
|
-
})
|