diff --git a/package.json b/package.json
index e2fb0c9..88f7fe8 100644
--- a/package.json
+++ b/package.json
@@ -1,6 +1,6 @@
{
"name": "@observation.org/react-native-components",
- "version": "1.57.0",
+ "version": "1.58.0",
"main": "src/index.ts",
"repository": "git@github.com:observation/react-native-components.git",
"author": "Observation.org",
@@ -52,7 +52,8 @@
"@react-navigation/native": "^6.0.8",
"react": "19.0.0",
"react-native": "0.79.1",
- "react-native-render-html": "^6.1.0"
+ "react-native-render-html": "^6.1.0",
+ "react-native-safe-area-context": "^5.6.0"
},
"dependencies": {
"@fortawesome/fontawesome-svg-core": "^6.7.2",
@@ -65,6 +66,7 @@
"accordion-collapse-react-native": "^1.1.1",
"color": "^4.2.3",
"react-native-logs": "^5.3.0",
+ "react-native-safe-area-context": "^5.6.0",
"react-native-scalable-image": "^1.1.0"
},
"packageManager": "yarn@3.6.4",
diff --git a/src/components/Lightbox.tsx b/src/components/Lightbox.tsx
index 0625daa..55b7bb5 100644
--- a/src/components/Lightbox.tsx
+++ b/src/components/Lightbox.tsx
@@ -1,8 +1,9 @@
import React, { useState } from 'react'
-import { SafeAreaView, StyleSheet, Text, TextStyle, TouchableOpacity, View } from 'react-native'
+import { StyleSheet, Text, TextStyle, TouchableOpacity, View } from 'react-native'
import ImageView from '@observation.org/react-native-image-viewing'
import Color from 'color'
+import { useSafeAreaInsets } from 'react-native-safe-area-context'
import { Icon } from './Icon'
import PageIndicator from './PageIndicator'
@@ -14,26 +15,29 @@ const hitSlop = { top: 16, left: 16, bottom: 16, right: 16 }
const getLightboxHeaderComponent =
(numberOfPages: number, onClose: () => void) =>
- ({ imageIndex }: { imageIndex: number }) => (
-
-
-
-
-
-
-
- onClose()} hitSlop={hitSlop}>
-
-
+ ({ imageIndex }: { imageIndex: number }) => {
+ const insets = useSafeAreaInsets()
+ return (
+
+
+
+
+
+
+
+ onClose()} hitSlop={hitSlop}>
+
+
+
-
- )
+ )
+ }
const getLightboxFooterComponent =
(
@@ -44,41 +48,44 @@ const getLightboxFooterComponent =
onPressDelete?: () => void,
onPressCrop?: () => void,
) =>
- () => (
-
-
- {title && (
-
- {title}
-
- )}
- {description && (
-
- {description}
-
- )}
- {content && {content}}
- {(onPressDelete || onPressCrop) && (
-
- {onPressDelete && (
-
-
-
-
-
- )}
- {onPressCrop && (
-
-
-
-
-
- )}
-
- )}
+ () => {
+ const insets = useSafeAreaInsets()
+ return (
+
+
+ {title && (
+
+ {title}
+
+ )}
+ {description && (
+
+ {description}
+
+ )}
+ {content && {content}}
+ {(onPressDelete || onPressCrop) && (
+
+ {onPressDelete && (
+
+
+
+
+
+ )}
+ {onPressCrop && (
+
+
+
+
+
+ )}
+
+ )}
+
-
- )
+ )
+ }
type LightboxStyle = {
descriptionTextStyle: TextStyle
diff --git a/src/components/__tests__/ContentImage.test.tsx b/src/components/__tests__/ContentImage.test.tsx
index 1a6c2f5..b246caf 100644
--- a/src/components/__tests__/ContentImage.test.tsx
+++ b/src/components/__tests__/ContentImage.test.tsx
@@ -1,44 +1,90 @@
import React from 'react'
import { fireEvent, render } from '@testing-library/react-native'
+import { SafeAreaProvider } from 'react-native-safe-area-context'
import ContentImage from '../ContentImage'
jest.mock('react-native-scalable-image', () => 'mock-scalable-image')
+const initialMetrics = {
+ frame: { x: 0, y: 0, width: 0, height: 0 },
+ insets: {
+ left: 0,
+ top: 59,
+ right: 0,
+ bottom: 34,
+ },
+}
+
describe('ContentImage', () => {
describe('Rendering', () => {
test('With alt title and text', () => {
- const { toJSON } = render()
+ const { toJSON } = render(
+
+
+ ,
+ )
expect(toJSON()).toMatchSnapshot()
})
test('With alt title', () => {
- const { toJSON } = render()
+ const { toJSON } = render(
+
+
+ ,
+ )
expect(toJSON()).toMatchSnapshot()
})
test('With alt title ending in delimiter', () => {
- const { toJSON } = render()
+ const { toJSON } = render(
+
+
+ ,
+ )
expect(toJSON()).toMatchSnapshot()
})
test('Without alt title', () => {
- const { toJSON } = render()
+ const { toJSON } = render(
+
+
+ ,
+ )
expect(toJSON()).toMatchSnapshot()
})
})
describe('Interaction', () => {
test('Click the image to show the lightbox', async () => {
- const { toJSON, getByTestId } = render()
+ const { toJSON, getByTestId } = render(
+
+
+ ,
+ )
+ await fireEvent(getByTestId('photo'), 'press')
+
+ expect(toJSON()).toMatchSnapshot()
+ })
+
+ test('Close the lightbox', async () => {
+ const { toJSON, getByTestId } = render(
+
+
+ ,
+ )
await fireEvent(getByTestId('photo'), 'press')
expect(toJSON()).toMatchSnapshot()
})
test('Close the lightbox', async () => {
- const { toJSON, getByTestId } = render()
+ const { toJSON, getByTestId } = render(
+
+
+ ,
+ )
await fireEvent(getByTestId('photo'), 'press')
await fireEvent(getByTestId('close-lightbox'), 'press')
expect(toJSON()).toMatchSnapshot()
diff --git a/src/components/__tests__/Lightbox.test.tsx b/src/components/__tests__/Lightbox.test.tsx
index 3587c82..a142e5a 100644
--- a/src/components/__tests__/Lightbox.test.tsx
+++ b/src/components/__tests__/Lightbox.test.tsx
@@ -2,6 +2,7 @@ import React from 'react'
import { Text } from 'react-native'
import { act, fireEvent, render } from '@testing-library/react-native'
+import { SafeAreaProvider } from 'react-native-safe-area-context'
import Lightbox from '../Lightbox'
@@ -15,6 +16,16 @@ const content = () => Jan de Vogelaar
let onClose: () => void
+const initialMetrics = {
+ frame: { x: 0, y: 0, width: 0, height: 0 },
+ insets: {
+ left: 0,
+ top: 59,
+ right: 0,
+ bottom: 34,
+ },
+}
+
let mockOnImageIndexChange = jest.fn()
jest.mock('@observation.org/react-native-image-viewing', () => {
const actualModule = jest.requireActual('@observation.org/react-native-image-viewing')
@@ -32,7 +43,9 @@ describe('Lightbox', () => {
describe('Rendering', () => {
test('First photo', () => {
const { toJSON, queryByText } = render(
- ,
+
+
+ ,
)
expect(queryByText('Grey Wagtail')).not.toBeNull()
expect(queryByText('11/26/2020 2:37 PM')).not.toBeNull()
@@ -41,7 +54,9 @@ describe('Lightbox', () => {
test('Second photo', () => {
const { toJSON, queryByText } = render(
- ,
+
+
+ ,
)
expect(queryByText('Grey Wagtail')).not.toBeNull()
expect(queryByText('11/26/2020 2:37 PM')).not.toBeNull()
@@ -49,21 +64,27 @@ describe('Lightbox', () => {
})
test('Only a photo', () => {
- const { toJSON } = render()
+ const { toJSON } = render(
+
+
+ ,
+ )
expect(toJSON()).toMatchSnapshot()
})
test('Photo with species name, date and user', () => {
const { toJSON } = render(
- ,
+
+
+ ,
)
expect(toJSON()).toMatchSnapshot()
@@ -72,14 +93,16 @@ describe('Lightbox', () => {
test('Content that is dependent on the selected image', () => {
const imageIndexDependentContent = (imageIndex?: number) => Karel de {imageIndex}e
const { toJSON, queryByText } = render(
- ,
+
+
+ ,
)
expect(queryByText('Karel de 99e')).not.toBeNull()
@@ -88,7 +111,9 @@ describe('Lightbox', () => {
test('With a delete button', () => {
const { toJSON, queryByTestId } = render(
- {}} />,
+
+ {}} />
+ ,
)
expect(queryByTestId('delete-photo')).not.toBeNull()
@@ -97,7 +122,9 @@ describe('Lightbox', () => {
test('With a crop button', () => {
const { toJSON, queryByTestId } = render(
- {}} />,
+
+ {}} />
+ ,
)
expect(queryByTestId('crop-photo')).not.toBeNull()
@@ -108,7 +135,11 @@ describe('Lightbox', () => {
describe('Interaction', () => {
test('Press close button calls onClose', async () => {
// GIVEN
- const { getByTestId } = render()
+ const { getByTestId } = render(
+
+
+ ,
+ )
// WHEN
await fireEvent.press(getByTestId('close-lightbox'))
@@ -120,7 +151,11 @@ describe('Lightbox', () => {
test('Press delete button calls onDelete', async () => {
// GIVEN
const onDelete = jest.fn()
- const { getByTestId } = render()
+ const { getByTestId } = render(
+
+
+ ,
+ )
// WHEN
await fireEvent.press(getByTestId('delete-photo'))
@@ -132,7 +167,11 @@ describe('Lightbox', () => {
test('Press crop button calls onCrop', async () => {
// GIVEN
const onCrop = jest.fn()
- const { getByTestId } = render()
+ const { getByTestId } = render(
+
+
+ ,
+ )
// WHEN
await fireEvent.press(getByTestId('crop-photo'))
@@ -146,7 +185,11 @@ describe('Lightbox', () => {
jest.mock('@observation.org/react-native-image-viewing', () => 'ImageCarousel')
const onDelete = jest.fn()
- const { getByTestId } = render()
+ const { getByTestId } = render(
+
+
+ ,
+ )
// WHEN
act(() => mockOnImageIndexChange(1))
diff --git a/src/components/__tests__/__snapshots__/ContentImage.test.tsx.snap b/src/components/__tests__/__snapshots__/ContentImage.test.tsx.snap
index 0365d01..438bf9c 100644
--- a/src/components/__tests__/__snapshots__/ContentImage.test.tsx.snap
+++ b/src/components/__tests__/__snapshots__/ContentImage.test.tsx.snap
@@ -1,986 +1,1553 @@
// Jest Snapshot v1, https://goo.gl/fbAQLP
exports[`ContentImage Interaction Click the image to show the lightbox 1`] = `
-
-
-
-
-
-
-
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
-
+
+ Title
+
-
+
-
+
+
+
+
+
+
+
+ Title
+
+
+
+
+
+
+
+`;
+
+exports[`ContentImage Interaction Close the lightbox 1`] = `
+
+
+
+
+
-
+
-
-
+
+
-
-
-
-
-
+
+
+
+
-
+
-
-
-
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
-
- Title
-
+
+ Title
+
+
-
+
-
-
-
+
-
-
-
-
-
+
- Title
-
+ />
+
+
+
+
+ Title
+
+
-
+
`;
-exports[`ContentImage Interaction Close the lightbox 1`] = `
-
-
-
-
-
-
+
- Title
-
+ />
+
+
+
+
+ Title
+
+
-
+
`;
exports[`ContentImage Rendering With alt title 1`] = `
-
-
-
-
-
-
+
- Title
-
+ />
+
+
+
+
+ Title
+
+
-
+
`;
exports[`ContentImage Rendering With alt title and text 1`] = `
-
-
-
-
-
-
+
- Title
-
-
- Text
-
+ />
+
+
+
+
+ Title
+
+
+ Text
+
+
-
+
`;
exports[`ContentImage Rendering With alt title ending in delimiter 1`] = `
-
-
-
-
-
-
+
- Title
-
+ />
+
+
+
+
+ Title
+
+
-
+
`;
exports[`ContentImage Rendering Without alt title 1`] = `
-
+>
+
+
`;
diff --git a/src/components/__tests__/__snapshots__/Lightbox.test.tsx.snap b/src/components/__tests__/__snapshots__/Lightbox.test.tsx.snap
index 037e433..8511a97 100644
--- a/src/components/__tests__/__snapshots__/Lightbox.test.tsx.snap
+++ b/src/components/__tests__/__snapshots__/Lightbox.test.tsx.snap
@@ -1,124 +1,113 @@
// Jest Snapshot v1, https://goo.gl/fbAQLP
exports[`Lightbox Rendering Content that is dependent on the selected image 1`] = `
-
-
-
-
-
+
+ }
+ >
-
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ >
+
+
+
+
+
+
+
+
+
+
+
+
+ Grey Wagtail
+
+
+
+
+ 11/26/2020 2:37 PM
+
+
+
-
+
+ Karel de
+ 99
+ e
+
-
+
-
+
+`;
+
+exports[`Lightbox Rendering First photo 1`] = `
+
+
+
-
-
+ }
+ >
-
-
+
+
-
-
-
-
-
+
+
-
+
+
+
+
+
+
-
-
-
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
-
- Grey Wagtail
-
-
-
-
+ Grey Wagtail
+
+
+
- 11/26/2020 2:37 PM
-
-
-
-
- Karel de
- 99
- e
-
+
+ 11/26/2020 2:37 PM
+
+
-
+
-
-
+
+
`;
-exports[`Lightbox Rendering First photo 1`] = `
-
-
-
-
+
+
+
-
+ />
+
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
- Grey Wagtail
-
-
-
-
- 11/26/2020 2:37 PM
-
-
-
-
-
-
-
-`;
-
-exports[`Lightbox Rendering Only a photo 1`] = `
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-`;
-
-exports[`Lightbox Rendering Photo with species name, date and user 1`] = `
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
+
-
+
-
-
-
-
-
+
-
+
- Grey Wagtail
-
+ maximumZoomScale={1}
+ onScrollEndDrag={[Function]}
+ pinchGestureEnabled={true}
+ scrollEnabled={false}
+ scrollEventThrottle={1}
+ showsHorizontalScrollIndicator={false}
+ showsVerticalScrollIndicator={false}
+ style={
+ {
+ "height": 1334,
+ "width": 750,
+ }
+ }
+ >
+
+
+
+
+
+
+
+
-
- 11/26/2020 2:37 PM
-
-
+ />
+
+
+
+
-
- Jan de Vogelaar
-
-
+ />
-
+
-
-
+
+
`;
-exports[`Lightbox Rendering Second photo 1`] = `
-
-
-
-
+
@@ -1719,18 +1373,30 @@ exports[`Lightbox Rendering Second photo 1`] = `
style={
{
"alignSelf": "center",
- "aspectRatio": 1,
- "backgroundColor": "#939393",
- "borderRadius": 4,
- "margin": 4,
- "overflow": "hidden",
- "width": 8,
+ "flexDirection": "row",
}
}
- />
-
+
+
+ }
+ }
+ />
+
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
+
-
+
-
-
-
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
-
- Grey Wagtail
-
-
-
-
+ Grey Wagtail
+
+
+
+
+ 11/26/2020 2:37 PM
+
+
+
- 11/26/2020 2:37 PM
-
+
+ Jan de Vogelaar
+
+
-
+
-
-
+
+
`;
-exports[`Lightbox Rendering With a crop button 1`] = `
-
-
-
-
+
+
+
+
+
+
+ }
+ >
+
+
+
+
+
+
+
+
+
+
+
+
+ >
+
+
+
+
+
+
+
+
+
+
+
+
+ Grey Wagtail
+
+
+
-
+
+ 11/26/2020 2:37 PM
+
-
+
-
+
+`;
+
+exports[`Lightbox Rendering With a crop button 1`] = `
+
+
+
-
+
-
-
+
+
-
+
-
-
-
+
-
-
-
-
-
-
-
-
-
-
+
@@ -2444,298 +2328,216 @@ exports[`Lightbox Rendering With a crop button 1`] = `
onStartShouldSetResponder={[Function]}
style={
{
+ "alignItems": "center",
+ "alignSelf": "flex-end",
+ "justifyContent": "center",
"opacity": 1,
}
}
>
-
-
-
-
-`;
-
-exports[`Lightbox Rendering With a delete button 1`] = `
-
-
-
+
-
-
-
+
-
+
+
-
-
+ >
+
+
+
+
+
+
+
-
-
-
-
+ "width": 750,
+ }
+ }
+ />
-
-
-
+
-
+ "bottom": 0,
+ "position": "absolute",
+ "transform": [
+ {
+ "translateX": 0,
+ },
+ {
+ "translateY": 0,
+ },
+ ],
+ "width": "100%",
+ "zIndex": 1,
+ }
+ }
+ >
-
-
+
-
-
-
-
-
+
+ >
+
+
-
+
-
-
+
+
+
+`;
+
+exports[`Lightbox Rendering With a delete button 1`] = `
+
+
-
+
+
+
+
+
+
+
@@ -2875,23 +2781,271 @@ exports[`Lightbox Rendering With a delete button 1`] = `
onStartShouldSetResponder={[Function]}
style={
{
+ "alignItems": "center",
+ "alignSelf": "flex-end",
+ "justifyContent": "center",
"opacity": 1,
}
}
>
-
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
-
-
+
+
`;
diff --git a/yarn.lock b/yarn.lock
index 635d549..9356aba 100644
--- a/yarn.lock
+++ b/yarn.lock
@@ -2433,6 +2433,7 @@ __metadata:
react-native: 0.79.1
react-native-logs: ^5.3.0
react-native-render-html: ^6.3.4
+ react-native-safe-area-context: ^5.6.0
react-native-scalable-image: ^1.1.0
react-test-renderer: 19.0.0
ts-jest: ^29.2.5
@@ -2443,6 +2444,7 @@ __metadata:
react: 19.0.0
react-native: 0.79.1
react-native-render-html: ^6.1.0
+ react-native-safe-area-context: ^5.6.0
languageName: unknown
linkType: soft
@@ -9398,6 +9400,16 @@ __metadata:
languageName: node
linkType: hard
+"react-native-safe-area-context@npm:^5.6.0":
+ version: 5.6.0
+ resolution: "react-native-safe-area-context@npm:5.6.0"
+ peerDependencies:
+ react: "*"
+ react-native: "*"
+ checksum: 23f382a6504dc6ff1064fe45af53207014a27d3312eeaa5e0fd92281c425cb518d7e5e7bc79cf29b0f8e685a30931df93800dfe98e5d81bd768499aa5e737ab0
+ languageName: node
+ linkType: hard
+
"react-native-scalable-image@npm:^1.1.0":
version: 1.1.0
resolution: "react-native-scalable-image@npm:1.1.0"