Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions .nvmrc
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
v22.22.2
54 changes: 54 additions & 0 deletions __mocks__/@gorhom/bottom-sheet.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,54 @@
/* eslint-disable observation/no-function-without-logging */
import React from 'react'
import { TouchableOpacity, View } from 'react-native'

import mockBottomSheet from '@gorhom/bottom-sheet/mock'

class MockBottomSheetModal extends React.Component {

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Wordt deze mock ergens gebruikt? Zo te zien niet, en dan kan ie weg.

snapToIndex() {}
snapToPosition() {}
expand() {}
collapse() {}
close() {}
forceClose() {}
present() {}
dismiss() {}

render() {
const mockAnimatedPosition = { value: 0 }
return (
<>
{this.props.children}
{this.props.backdropComponent({ animatedPosition: mockAnimatedPosition })}
</>
)
}
}

class MockBottomSheet extends React.Component {
snapToIndex() {}
snapToPosition() {}
expand() {}
collapse() {}
close() {
this.props.onClose?.()
}
forceClose() {}

render() {
return (
<View testID="bottom-sheet">
{this.props.handleComponent?.()}
{this.props.children}
{this.props.backdropComponent()}
</View>
)
}
}

module.exports = {
__esModule: true,
...mockBottomSheet,
default: MockBottomSheet,
BottomSheetModal: MockBottomSheetModal,
}
10 changes: 10 additions & 0 deletions jest.mocks.js
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
import React from 'react'
import { Image } from 'react-native'
import 'react-native-gesture-handler/jestSetup'
import mockSafeAreaContext from 'react-native-safe-area-context/jest/mock'

// === Globals ===

Expand Down Expand Up @@ -60,6 +61,15 @@ jest.mock('@fortawesome/react-native-fontawesome', () => {

Image.getSizeWithHeaders = jest.fn(() => Promise.resolve({ width: 0, height: 0 }))

// Mock out configureNext for LayoutAnimation
jest.mock('react-native/Libraries/LayoutAnimation/LayoutAnimation', () => ({
...jest.requireActual('react-native/Libraries/LayoutAnimation/LayoutAnimation'),
configureNext: jest.fn(),
}))

jest.mock('react-native-worklets', () => require('react-native-worklets/src/mock'))

jest.mock('react-native-reanimated', () => require('react-native-reanimated/mock'))

jest.mock('react-native-safe-area-context', () => mockSafeAreaContext)

9 changes: 8 additions & 1 deletion package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "@observation.org/react-native-components",
"version": "1.90.0",
"version": "1.91.0",
"main": "src/index.ts",
"exports": {
".": "./src/index.ts",
Expand All @@ -13,6 +13,7 @@
"devDependencies": {
"@eslint/eslintrc": "^3.3.6",
"@eslint/js": "^9.39.5",
"@gorhom/bottom-sheet": ">=5.2.14",

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Dit is te open, in devDependencies zetten we de major version vast:

Suggested change
"@gorhom/bottom-sheet": ">=5.2.14",
"@gorhom/bottom-sheet": "^5.2.14",

"@react-native-community/blur": "^4.4.1",
"@react-native/babel-preset": "0.83.1",
"@react-native/typescript-config": "0.83.1",
Expand Down Expand Up @@ -41,6 +42,7 @@
"react-native-gesture-handler": "^2.32.0",
"react-native-reanimated": "4.5.3",
"react-native-render-html": "^6.3.4",
"react-native-safe-area-context": "^5.8.0",
"react-native-worklets": "0.11.3",
"react-test-renderer": "19.2.0",
"ts-jest": "^29.4.6",
Expand All @@ -60,13 +62,15 @@
"src"
],
"peerDependencies": {
"@gorhom/bottom-sheet": ">=5.2.9",
"@react-native-community/blur": ">=4.4.1",
"@react-navigation/native": "^7.0.0",
"react": "^19.0.0",
"react-native": ">=0.83.0",
"react-native-gesture-handler": ">=2.16.1",
"react-native-reanimated": ">=4.0.0",
"react-native-render-html": "^6.1.0",
"react-native-safe-area-context": ">=5.7.0",
"react-native-svg": ">=15.0.0",
"react-native-worklets": ">=0.8.0"
},
Expand Down Expand Up @@ -107,5 +111,8 @@
"react-test-renderer"
]
}
},
"resolutions": {
"react-is": "19.2.3"

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Dit bleek nodig te zijn om een rare fout in de tests op te lossen. Zit ook in de 2 apps. Ik kwam erop na het lezen van dit issue.

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Die was ook nodig in de apps, ik kan me nog herinneren dat dit een lastige was om te debuggen.

}
}
193 changes: 193 additions & 0 deletions src/components/BottomSheetWithSearchInput.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,193 @@
import React, { useCallback, useMemo, useRef, useState } from 'react'
import { Keyboard, StyleSheet, View } from 'react-native'

import BottomSheet, {
BottomSheetBackdrop,
BottomSheetBackdropProps,
BottomSheetFlatList,
BottomSheetFlatListMethods,
BottomSheetTextInput,
TouchableOpacity,
useBottomSheetTimingConfigs,
} from '@gorhom/bottom-sheet'
import { Icon, InputField, ItemSeparator, ListItem, SectionHeader } from '@observation.org/react-native-components'
import { type Theme, createInputStyles, useStyles, useTheme } from '@observation.org/react-native-components/theme'
Comment on lines +13 to +14

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

De library krijgt zo een dependency op zichzelf. Dit kan gewoon een intern pad zijn. Hetzelfde geldt voor nog 5 source files, goed om meteen ook aan te passen.

import { useTheme as useNavigationTheme } from '@react-navigation/native'
import { Easing } from 'react-native-reanimated'
import { useSafeAreaInsets } from 'react-native-safe-area-context'

import useBottomSheetBackHandler from '../hooks/useBottomSheetBackHandler'
import Log from '../lib/Log'
import { createBottomSheetStyles } from '../styles/bottomSheet'

type Props = {
onClose: () => void
onPress: (key: string) => void
inputFieldPlaceholder: string
data: DataItem[]
selectedKey?: string
sectionTitle: string
children?: React.ReactNode
listRef?: React.RefObject<BottomSheetFlatListMethods | null>
}

type DataItem = {
key: string
label: string
}

const Separator = () => <ItemSeparator />

const BottomSheetWithSearchInput = ({
onClose,
onPress,
inputFieldPlaceholder,
data,
selectedKey,
sectionTitle,
children,
listRef,
}: Props) => {
const theme = useTheme()
const inputStyles = createInputStyles(theme)
const styles = useMemo(() => createStyles(theme, inputStyles), [theme, inputStyles])

const [searchString, setSearchString] = useState('')
const bottomSheetRef = useRef<BottomSheet>(null)
const insets = useSafeAreaInsets()
const { colors } = useNavigationTheme()
const bottomSheetStyles = useStyles(createBottomSheetStyles)
const animationConfigs = useBottomSheetTimingConfigs({
duration: theme.animation.duration.medium,
easing: Easing.in(Easing.quad),
})

useBottomSheetBackHandler(bottomSheetRef)

const onPressBackdrop = () => {
Log.debug('BottomSheetWithSearchInput:onPressBackdrop')
Keyboard.dismiss()
}

const backdropComponent = useCallback(
(props: BottomSheetBackdropProps) => (
<BottomSheetBackdrop {...props} disappearsOnIndex={-1} appearsOnIndex={0} onPress={onPressBackdrop} />
),
[],
)

const onChange = useCallback((value: string) => {
Log.debug('BottomSheetWithSearchInput:onChange', value)
setSearchString(value)
}, [])

const onClear = useCallback(() => {
Log.debug('BottomSheetWithSearchInput:onClear')
setSearchString('')
}, [])

const handlePress = useCallback(
(item: DataItem) => {
Log.debug('BottomSheetWithSearchInput:handlePress', item)
Keyboard.dismiss()
onPress(item.key)
bottomSheetRef.current?.close()
},
[onPress],
)

const filteredData = useMemo(
() => data.filter((item) => item.label.toLowerCase().startsWith(searchString.toLowerCase())),
[data, searchString],
)

const listHeader = useMemo(
() => (
<View style={styles.headerContainer}>
{children}
<InputField
textInputComponent={BottomSheetTextInput}
containerStyle={styles.inputContainer}
inputStyle={styles.input}
inputProps={{
testID: 'textInput',
autoCorrect: false,
placeholder: inputFieldPlaceholder,
value: searchString,
onChangeText: onChange,
}}
rightIcon={
<TouchableOpacity

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Alternatief is om in de componenten-library een BottomSheetIconButton toe te voegen.

style={styles.iconButtonContainer}
onPress={onClear}
testID="clearButton"
activeOpacity={0.5}
>
<Icon
name="circle-xmark"
style="solid"
color={theme.color.icon.system.subtler}
size={theme.icon.size.s}
/>
</TouchableOpacity>
}
/>
<SectionHeader title={sectionTitle} />
</View>
),
[children, sectionTitle, searchString, onChange, onClear, styles, theme],
)

return (
<BottomSheet
ref={bottomSheetRef}
enablePanDownToClose
onClose={onClose}
backdropComponent={backdropComponent}
animationConfigs={animationConfigs}
snapPoints={['80%']}
enableDynamicSizing={false}
handleIndicatorStyle={bottomSheetStyles.handleIndicator}
handleStyle={[bottomSheetStyles.handle, { backgroundColor: colors.card }]}
keyboardBehavior="extend"
>
<BottomSheetFlatList<DataItem>
ref={listRef}
data={filteredData}
keyExtractor={(item) => item.key}
ItemSeparatorComponent={Separator}
keyboardDismissMode="on-drag"
keyboardShouldPersistTaps="handled"
contentContainerStyle={{ paddingBottom: insets.bottom, backgroundColor: colors.card }}
ListHeaderComponent={listHeader}
stickyHeaderIndices={[0]}
renderItem={({ item }) => (
<ListItem label={item.label} selected={item.key === selectedKey} onPress={() => handlePress(item)} />
)}
/>
</BottomSheet>
)
}

export default BottomSheetWithSearchInput

const createStyles = (theme: Theme, inputStyles: ReturnType<typeof createInputStyles>) => {
const styles = StyleSheet.create({
headerContainer: {
backgroundColor: theme.color.background.system.surfaceRaised,
},
inputContainer: {
marginHorizontal: theme.margin.half,
marginTop: theme.margin.common,
backgroundColor: theme.color.background.system.surfaceBase,
},
input: {
...theme.text.input,
...inputStyles.input,
},
iconButtonContainer: {
paddingRight: theme.margin.common,
},
})
return styles
}
Loading