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"