From c5fac39b0ee42ec7bbd9a5409aa3c45952803cc3 Mon Sep 17 00:00:00 2001 From: Sanchitv3 Date: Fri, 3 Jan 2025 13:30:26 +0530 Subject: [PATCH 1/5] feat: created compound api and currently displaying single image --- example/storybook-nativewind/babel.config.js | 4 + example/storybook-nativewind/package.json | 3 +- .../ImageViewer/ImageViewer.stories.tsx | 18 ++ .../components/ImageViewer/ImageViewer.tsx | 46 +++++ .../ImageViewer/index.nw.stories.mdx | 174 ++++++++++++++++++ .../nativewind/image-viewer/index.tsx | 113 ++++++++++++ .../nativewind/image/index.tsx | 2 + example/storybook-nativewind/tsconfig.json | 4 +- packages/unstyled/image-viewer/.npmignore | 20 ++ packages/unstyled/image-viewer/CHANGELOG.md | 5 + packages/unstyled/image-viewer/README.md | 50 +++++ .../unstyled/image-viewer/babel.config.js | 13 ++ packages/unstyled/image-viewer/package.json | 79 ++++++++ .../unstyled/image-viewer/src/ImageViewer.tsx | 34 ++++ .../image-viewer/src/ImageViewerBackdrop.tsx | 26 +++ .../src/ImageViewerCloseButton.tsx | 69 +++++++ .../image-viewer/src/ImageViewerContent.tsx | 152 +++++++++++++++ .../image-viewer/src/ImageViewerContext.ts | 9 + packages/unstyled/image-viewer/src/index.tsx | 47 +++++ packages/unstyled/image-viewer/src/types.ts | 26 +++ packages/unstyled/image-viewer/tsconfig.json | 31 ++++ yarn.lock | 173 ++++++++++------- 22 files changed, 1024 insertions(+), 74 deletions(-) create mode 100644 example/storybook-nativewind/src/components/ImageViewer/ImageViewer.stories.tsx create mode 100644 example/storybook-nativewind/src/components/ImageViewer/ImageViewer.tsx create mode 100644 example/storybook-nativewind/src/components/ImageViewer/index.nw.stories.mdx create mode 100644 example/storybook-nativewind/src/core-components/nativewind/image-viewer/index.tsx create mode 100644 packages/unstyled/image-viewer/.npmignore create mode 100644 packages/unstyled/image-viewer/CHANGELOG.md create mode 100644 packages/unstyled/image-viewer/README.md create mode 100644 packages/unstyled/image-viewer/babel.config.js create mode 100644 packages/unstyled/image-viewer/package.json create mode 100644 packages/unstyled/image-viewer/src/ImageViewer.tsx create mode 100644 packages/unstyled/image-viewer/src/ImageViewerBackdrop.tsx create mode 100644 packages/unstyled/image-viewer/src/ImageViewerCloseButton.tsx create mode 100644 packages/unstyled/image-viewer/src/ImageViewerContent.tsx create mode 100644 packages/unstyled/image-viewer/src/ImageViewerContext.ts create mode 100644 packages/unstyled/image-viewer/src/index.tsx create mode 100644 packages/unstyled/image-viewer/src/types.ts create mode 100644 packages/unstyled/image-viewer/tsconfig.json diff --git a/example/storybook-nativewind/babel.config.js b/example/storybook-nativewind/babel.config.js index f926844845..97f0e5ac34 100644 --- a/example/storybook-nativewind/babel.config.js +++ b/example/storybook-nativewind/babel.config.js @@ -34,6 +34,10 @@ module.exports = function (api) { __dirname, '../../packages/unstyled/input/src' ), + '@gluestack-ui/image-viewer': path.resolve( + __dirname, + '../../packages/unstyled/image-viewer/src' + ), '@gluestack-ui/tooltip': path.resolve( __dirname, '../../packages/unstyled/tooltip/src' diff --git a/example/storybook-nativewind/package.json b/example/storybook-nativewind/package.json index ade5bc4cd7..dd28db7b85 100644 --- a/example/storybook-nativewind/package.json +++ b/example/storybook-nativewind/package.json @@ -52,8 +52,8 @@ "@react-native-community/slider": "4.2.4", "@react-stately/collections": "^3.6.0", "@react-stately/tree": "^3.5.0", - "@unitools/link": "^0.0.4", "@unitools/image": "^0.0.5", + "@unitools/link": "^0.0.4", "expo": "^47.0.0", "expo-linear-gradient": "^12.3.0", "expo-status-bar": "~1.4.2", @@ -66,7 +66,6 @@ "react": "^18.2.0", "react-dom": "^18.2.0", "react-native": "0.72.4", - "react-native-gesture-handler": "~2.14.0", "react-native-reanimated": "~3.6.2", "react-native-safe-area-context": "^4.4.1", "react-native-svg": "13.4.0", diff --git a/example/storybook-nativewind/src/components/ImageViewer/ImageViewer.stories.tsx b/example/storybook-nativewind/src/components/ImageViewer/ImageViewer.stories.tsx new file mode 100644 index 0000000000..7a4016ac0e --- /dev/null +++ b/example/storybook-nativewind/src/components/ImageViewer/ImageViewer.stories.tsx @@ -0,0 +1,18 @@ +import type { ComponentMeta } from '@storybook/react-native'; +import ImageViewer from './ImageViewer'; + +const ImageViewerMeta: ComponentMeta = { + title: 'stories/ImageViewer', + component: ImageViewer, + // metaInfo is required for figma generation + // @ts-ignore + metaInfo: { + componentDescription: `With the Image component, you can enhance the look and feel of your app or website by incorporating compelling imagery.`, //change description + }, + argTypes: {}, + args: {}, +}; + +export default ImageViewerMeta; + +export { ImageViewer }; diff --git a/example/storybook-nativewind/src/components/ImageViewer/ImageViewer.tsx b/example/storybook-nativewind/src/components/ImageViewer/ImageViewer.tsx new file mode 100644 index 0000000000..6ce66e87dd --- /dev/null +++ b/example/storybook-nativewind/src/components/ImageViewer/ImageViewer.tsx @@ -0,0 +1,46 @@ +'use client'; +import React, { useState } from 'react'; +import { Image, Pressable } from 'react-native'; +import { + ImageViewer, + ImageViewerBackdrop, + ImageViewerContent, + ImageViewerImage, +} from '@/components/ui/image-viewer'; + +const ImageViewerBasic = () => { + const Images = [ + { id: 1, url: 'https://picsum.photos/200/300' }, + { id: 2, url: 'https://picsum.photos/200/300' }, + { id: 3, url: 'https://picsum.photos/200/300' }, + ]; + const [visible, setVisible] = useState(false); + return ( + <> + setVisible(true)}> + + + + setVisible(false)}> + + ( + + )} + /> + + + + ); +}; + +ImageViewerBasic.description = 'This is a basic ImageViewer component example.'; + +export default ImageViewerBasic; + +export { ImageViewer }; diff --git a/example/storybook-nativewind/src/components/ImageViewer/index.nw.stories.mdx b/example/storybook-nativewind/src/components/ImageViewer/index.nw.stories.mdx new file mode 100644 index 0000000000..01339038dd --- /dev/null +++ b/example/storybook-nativewind/src/components/ImageViewer/index.nw.stories.mdx @@ -0,0 +1,174 @@ +--- +title: gluestack-ui Image Component | Installation, Usage, and API + +description: Enhance your app or website with captivating visuals using our Image feature. + +pageTitle: Image + +pageDescription: Enhance your app or website with captivating visuals using our Image feature. + +showHeader: true +--- + +import { Meta } from '@storybook/addon-docs'; + + + +import { ImageViewer } from '../../core-components/nativewind'; +import { transformedCode } from '../../utils'; +import Wrapper from '../../core-components/nativewind/Wrapper'; +import UnitoolsImageEg from '../../extra-components/nativewind/UnitoolsImage'; + +import { + AppProvider, + CodePreview, + Text, + Table, + TableContainer, + InlineCode, + Tabs +} from '@gluestack/design-system'; +import { CollapsibleCode } from '@gluestack/design-system'; +import UnitoolsImage from '@unitools/image'; + +This is an illustration of **Image** component. + + + { + return transformedCode(code); + }, + scope: { + Wrapper, + ImageViewer, + }, + argsType: { + size: { + control: 'select', + options: ['2xs', 'xs', 'sm', 'md', 'lg', 'xl', '2xl'], + default: 'md', + }, + }, + }} + /> + + +
+ +## Installation + + + + + CLI + + + Manual + + + + +<> + +### Run the following command: + ```bash + npx gluestack-ui add image + ``` + + + +<> + +### Step 1: Install the following dependencies: +```bash +npm i @gluestack-ui/image +``` + +### Step 2: Copy and paste the following code into your project. + + +```jsx +%%-- File: core-components/nativewind/image/index.tsx --%% +``` + + +### Step 3: Update the import paths to match your project setup. + + + + + +## API Reference + +To use this component in your project, include the following import statement in your file. + +```jsx +import { Image } from '@/components/ui/image'; +``` + +```jsx +export default () => ; +``` + +### Component Props + +This section provides a comprehensive reference list for the component props, detailing descriptions, properties, types, and default behavior for easy project integration. + +#### Image + +It inherits all the properties of React Native's [Image](https://reactnative.dev/docs/image) component. + +### Props + +Image component is created using Image component from react-native. It extends all the props supported by [React Native Image](https://reactnative.dev/docs/image#props). + +### Examples + +The Examples section offers visual previews of the component, letting you quickly identify the best fit for your needs. Just copy the code and use it in your project. + +#### Basic Unitools Image + +The below example will run for both Expo and Next.js projects. For installation steps, refer to the [Installation](https://unitools.geekyants.com/packages/image/) section of ```@unitools/image```. + +```jsx +import Image from '@unitools/image'; +``` + + + + + + `, + transformCode: (code) => { + return transformedCode(code); + }, + scope: { + Wrapper, + UnitoolsImage:Image, + }, + argsType: { + }, + }} + /> + \ No newline at end of file diff --git a/example/storybook-nativewind/src/core-components/nativewind/image-viewer/index.tsx b/example/storybook-nativewind/src/core-components/nativewind/image-viewer/index.tsx new file mode 100644 index 0000000000..9c29a39a1f --- /dev/null +++ b/example/storybook-nativewind/src/core-components/nativewind/image-viewer/index.tsx @@ -0,0 +1,113 @@ +'use client'; +import React from 'react'; +import { createImageViewer } from '@gluestack-ui/image-viewer'; +import { Image, Modal, Pressable } from 'react-native'; +import { tva } from '@gluestack-ui/nativewind-utils/tva'; +import { + GestureHandlerRootView, + GestureDetector, + Gesture, +} from 'react-native-gesture-handler'; + +import Animated from 'react-native-reanimated'; +const imageViewerStyle = tva({ + base: 'flex-1 bg-background-dark justify-center items-center ', +}); + +const ImageStyle = tva({ + base: 'w-[100vw] h-[100vh]', +}); + +const BackdropStyle = tva({ + base: 'flex-1 bg-background-dark', +}); + +const CloseButtonStyle = tva({ + base: 'absolute top-4 right-4 z-10 bg-background-dark rounded-full w-8 h-8 justify-center items-center cursor-pointer', +}); + +const UIImageViewer = createImageViewer({ + Root: Modal, + Backdrop: Animated.View, + Content: GestureHandlerRootView, + Animated: Animated.View, + Gesture: Gesture, + GestureDetector: GestureDetector, + CloseButton: Pressable, +}); + +// type ImageViewerProps = VariantProps & +// React.ComponentProps; + +const ImageViewer = React.forwardRef(({ className, ...props }: any, ref) => { + return ( + + ); +}); + +const ImageViewerBackdrop = React.forwardRef( + ({ className, ...props }: any, ref) => { + return ( + + ); + } +); + +const ImageViewerContent = React.forwardRef( + ({ className, ...props }: any, ref) => { + return ( + + ); + } +); + +const ImageViewerCloseButton = React.forwardRef( + ({ className, ...props }: any, ref) => { + return ( + + ); + } +); + +const ImageViewerImage = React.forwardRef( + ({ className, ...props }: any, ref) => { + return ( + + ); + } +); + +ImageViewer.displayName = 'ImageViewer'; +ImageViewerBackdrop.displayName = 'ImageViewerBackdrop'; +ImageViewerContent.displayName = 'ImageViewerContent'; +ImageViewerCloseButton.displayName = 'ImageViewerCloseButton'; +ImageViewerImage.displayName = 'ImageViewerImage'; + +export { + ImageViewer, + ImageViewerBackdrop, + ImageViewerContent, + ImageViewerCloseButton, + ImageViewerImage, +}; diff --git a/example/storybook-nativewind/src/core-components/nativewind/image/index.tsx b/example/storybook-nativewind/src/core-components/nativewind/image/index.tsx index 1afc87a2d7..76eb897bc2 100644 --- a/example/storybook-nativewind/src/core-components/nativewind/image/index.tsx +++ b/example/storybook-nativewind/src/core-components/nativewind/image/index.tsx @@ -4,6 +4,7 @@ import { createImage } from '@gluestack-ui/image'; import { Platform, Image as RNImage } from 'react-native'; import { tva } from '@gluestack-ui/nativewind-utils/tva'; import type { VariantProps } from '@gluestack-ui/nativewind-utils'; +import { cssInterop } from 'nativewind'; const imageStyle = tva({ base: 'max-w-full', @@ -23,6 +24,7 @@ const imageStyle = tva({ }); const UIImage = createImage({ Root: RNImage }); +cssInterop(UIImage, { className: { target: 'style' } }); type ImageProps = VariantProps & React.ComponentProps; diff --git a/example/storybook-nativewind/tsconfig.json b/example/storybook-nativewind/tsconfig.json index 798a40bd4f..b72af97f55 100644 --- a/example/storybook-nativewind/tsconfig.json +++ b/example/storybook-nativewind/tsconfig.json @@ -17,7 +17,6 @@ "@gluestack-ui/form-control": [ "../../packages/unstyled/form-control/src" ], - "@/components/ui/utils/*": ["src/core-components/hooks/*"], "@gluestack-ui/modal": ["../../packages/unstyled/modal/src"], "@gluestack-ui/radio": ["../../packages/unstyled/radio/src"], "@gluestack-ui/accordion": ["../../packages/unstyled/accordion/src"], @@ -26,6 +25,9 @@ "@gluestack-ui/tooltip": ["../../packages/unstyled/tooltip/src"], "@gluestack-ui/fab": ["../../packages/unstyled/fab/src"], "@gluestack-ui/progress": ["../../packages/unstyled/progress/src"], + "@gluestack-ui/image-viewer": [ + "../../packages/unstyled/image-viewer/src" + ], "@gluestack-ui/alert-dialog": [ "../../packages/unstyled/alert-dialog/src" ], diff --git a/packages/unstyled/image-viewer/.npmignore b/packages/unstyled/image-viewer/.npmignore new file mode 100644 index 0000000000..187790b632 --- /dev/null +++ b/packages/unstyled/image-viewer/.npmignore @@ -0,0 +1,20 @@ +# Dotfiles +.babelrc +.eslintignore +.eslintrc.json +.gitattributes +_config.yml +.editorconfig + + +#Config files +babel.config.js + +# Documents +CONTRIBUTING.md +ISSUE_TEMPLATE.txt +img + +# Test cases +__tests__ +dist/__tests__ diff --git a/packages/unstyled/image-viewer/CHANGELOG.md b/packages/unstyled/image-viewer/CHANGELOG.md new file mode 100644 index 0000000000..a03b580b49 --- /dev/null +++ b/packages/unstyled/image-viewer/CHANGELOG.md @@ -0,0 +1,5 @@ +# @gluestack-ui/image-viewer + +## 0.0.1 + +- Initial release diff --git a/packages/unstyled/image-viewer/README.md b/packages/unstyled/image-viewer/README.md new file mode 100644 index 0000000000..7964c25888 --- /dev/null +++ b/packages/unstyled/image-viewer/README.md @@ -0,0 +1,50 @@ +# @gluestack-ui/image-viewer + +## Installation + +To use `@gluestack-ui/image-viewer`, all you need to do is install the +`@gluestack-ui/image-viewer` package: + +```sh +$ yarn add @gluestack-ui/image-viewer + +# or + +$ npm i @gluestack-ui/image-viewer +``` + +## Usage + +A image component is a graphical user interface element that enables users to act by clicking or tapping. It can be customized in size, shape, color, and behavior to fit the design of the application or website. Here's an example how to use this package to create one: + +```jsx +import { createImage } from '@gluestack-ui/image'; +import { Root } from './styled-components'; + +export const Image = createImage({ + Root, +}); +``` + +## Customizing the Image: + +Default styling of all these components can be found in the components/core/image file. For reference, you can view the [source code](https://github.com/gluestack/gluestack-ui/blob/development/example/storybook/src/ui-components/Image/index.tsx) of the styled `Image` components. + +```jsx +// import the styles +import { Root } from '../components/core/image/styled-components'; + +// import the createImage function +import { createImage } from '@gluestack-ui/image'; + +// Understanding the API +const Image = createImage({ + Root, +}); + +// Using the image component +export default () => ; +``` + +More guides on how to get started are available +[here](https://ui.gluestack.io/docs/components/forms/image). diff --git a/packages/unstyled/image-viewer/babel.config.js b/packages/unstyled/image-viewer/babel.config.js new file mode 100644 index 0000000000..b2d6e52495 --- /dev/null +++ b/packages/unstyled/image-viewer/babel.config.js @@ -0,0 +1,13 @@ +const path = require('path'); + +module.exports = function (api) { + api.cache(true); + return { + presets: ['babel-preset-expo'], + plugins: [ + process.env.NODE_ENV !== 'production' + ? [] + : ['babel-plugin-react-docgen-typescript', { exclude: 'node_modules' }], + ], + }; +}; diff --git a/packages/unstyled/image-viewer/package.json b/packages/unstyled/image-viewer/package.json new file mode 100644 index 0000000000..f53b9be51f --- /dev/null +++ b/packages/unstyled/image-viewer/package.json @@ -0,0 +1,79 @@ +{ + "name": "@gluestack-ui/image-viewer", + "version": "0.0.1", + "main": "lib/index", + "module": "lib/index", + "types": "lib/index.d.ts", + "react-native": "src/index", + "source": "src/index", + "typings": "lib/index.d.ts", + "description": "A universal headless ImageViewer component for React Native, Next.js & React", + "keywords": [ + "react", + "native", + "react-native", + "image-viewer", + "gluestack-ui", + "universal", + "headless", + "typescript", + "component", + "android", + "ios", + "nextjs" + ], + "scripts": { + "prepare": "tsc", + "release": "release-it", + "watch": "tsc --watch", + "build": "tsc", + "clean": "rm -rf lib", + "dev:web": "cd example/native && yarn web --clear", + "storybook": "cd example/native/storybook && yarn web" + }, + "devDependencies": { + "@types/react": "^18.0.22", + "@types/react-native": "^0.72.3", + "babel-plugin-transform-remove-console": "^6.9.4", + "react": "^18.1.0", + "react-dom": "^18.1.0", + "react-native": "^0.72.4", + "react-native-builder-bob": "^0.20.1", + "react-native-web": "^0.19.9", + "tsconfig": "7", + "typescript": "^5.6.3" + }, + "dependencies": { + "react-native-gesture-handler": "^2.21.2", + "react-native-reanimated": "~3.6.2" + }, + "peerDependencies": { + "react": ">=16", + "react-dom": ">=16" + }, + "homepage": "https://github.com/gluestack/gluestack-ui/tree/main/packages/unstyled/image-viewer#readme", + "repository": { + "type": "git", + "url": "git+https://github.com/gluestack/gluestack-ui.git" + }, + "files": [ + "lib/", + "src/" + ], + "jest": { + "preset": "jest-expo", + "transform": { + "^.+\\.js$": "/node_modules/react-native/jest/preprocessor.js" + }, + "modulePathIgnorePatterns": [ + "/example/*", + "/lib/" + ], + "transformIgnorePatterns": [ + "node_modules/(?!(@react-native|react-native|expo-asset|expo-constants|@unimodules|react-native-unimodules|expo-font|react-native-svg|@expo/vector-icons|react-native-vector-icons|@react-native-aria/checkbox|@react-native-aria/interactions|@react-native-aria/button|@react-native-aria/switch|@react-native-aria/toggle|@react-native-aria/utils|@react-native-aria/*))" + ], + "setupFiles": [ + "/src/jest/mock.ts" + ] + } +} diff --git a/packages/unstyled/image-viewer/src/ImageViewer.tsx b/packages/unstyled/image-viewer/src/ImageViewer.tsx new file mode 100644 index 0000000000..968e0cf533 --- /dev/null +++ b/packages/unstyled/image-viewer/src/ImageViewer.tsx @@ -0,0 +1,34 @@ +import React, { forwardRef } from 'react'; +import { ImageViewerContext } from './ImageViewerContext'; + +const ImageViewer = (StyledRoot: any) => + forwardRef(({ children, isOpen, onClose, ...props }: any, ref?: any) => { + const [scale, setScale] = React.useState(1); + + const contextValue = React.useMemo(() => { + return { + onClose, + isOpen, + scale, + setScale, + }; + }, [onClose, isOpen, scale]); + + return ( + + + {children} + + + ); + }); + +export default ImageViewer; diff --git a/packages/unstyled/image-viewer/src/ImageViewerBackdrop.tsx b/packages/unstyled/image-viewer/src/ImageViewerBackdrop.tsx new file mode 100644 index 0000000000..843e544075 --- /dev/null +++ b/packages/unstyled/image-viewer/src/ImageViewerBackdrop.tsx @@ -0,0 +1,26 @@ +import React, { forwardRef, useContext } from 'react'; +import { ImageViewerContext } from './ImageViewerContext'; +import { useAnimatedStyle } from 'react-native-reanimated'; + +const ImageViewerBackdrop = (StyledImageViewerBackdrop: any) => + forwardRef(({ children, ...props }: any, ref?: any) => { + const { scale } = useContext(ImageViewerContext); + const animatedStyle = useAnimatedStyle(() => { + return { + opacity: scale, + }; + }); + + return ( + + {children} + + ); + }); + +export default ImageViewerBackdrop; diff --git a/packages/unstyled/image-viewer/src/ImageViewerCloseButton.tsx b/packages/unstyled/image-viewer/src/ImageViewerCloseButton.tsx new file mode 100644 index 0000000000..f5cfead0e7 --- /dev/null +++ b/packages/unstyled/image-viewer/src/ImageViewerCloseButton.tsx @@ -0,0 +1,69 @@ +import React, { forwardRef } from 'react'; +import { useHover, usePress } from '@react-native-aria/interactions'; +import { composeEventHandlers } from '@gluestack-ui/utils'; +import { useFocusRing, useFocus } from '@react-native-aria/focus'; +import { ImageViewerContext } from './ImageViewerContext'; + +const ImageViewerCloseButton = (StyledImageViewerCloseButton: any) => + forwardRef((props: any, ref?: any) => { + const { hoverProps, isHovered } = useHover(); + const { pressProps, isPressed } = usePress({ + isDisabled: props.isDisabled, + }); + const { focusProps, isFocused } = useFocus(); + const { isFocusVisible, focusProps: focusRingProps }: any = useFocusRing(); + + const { + // _icon, + onPressIn, + onPressOut, + onHoverIn, + onHoverOut, + onFocus, + onBlur, + children, + ...resolvedProps + } = props; + const { onClose } = React.useContext(ImageViewerContext); + + return ( + + {children} + + ); + }); + +export default ImageViewerCloseButton; diff --git a/packages/unstyled/image-viewer/src/ImageViewerContent.tsx b/packages/unstyled/image-viewer/src/ImageViewerContent.tsx new file mode 100644 index 0000000000..e5cf7fcce5 --- /dev/null +++ b/packages/unstyled/image-viewer/src/ImageViewerContent.tsx @@ -0,0 +1,152 @@ +import React, { forwardRef, useContext } from 'react'; +import { ImageViewerContext } from './ImageViewerContext'; +import { + runOnJS, + useAnimatedStyle, + useSharedValue, + withSpring, + withTiming, +} from 'react-native-reanimated'; +import { Dimensions, StatusBar } from 'react-native'; + +const { width: SCREEN_WIDTH, height: SCREEN_HEIGHT } = Dimensions.get('window'); +const DOUBLE_TAP_DELAY = 300; + +const ImageViewerContent = ( + StyledGestureHandlerRootView: any, + StyledGestureDetector: any, + StyledAnimated: any, + Gesture: any +) => + forwardRef(({ images, renderImages, children }: any, ref?: any) => { + const { onClose, setScale }: any = useContext(ImageViewerContext); + const scale = useSharedValue(1); + const savedScale = useSharedValue(1); + const translateX = useSharedValue(0); + const translateY = useSharedValue(0); + const focalX = useSharedValue(0); + const focalY = useSharedValue(0); + + const pinchGesture = Gesture.Pinch() + .onStart(() => { + savedScale.value = scale.value; + }) + .onUpdate((event: any) => { + // Apply the new scale based on the saved scale value + const newScale = savedScale.value * event.scale; + scale.value = Math.min(Math.max(newScale, 0.5), 3); + focalX.value = event.focalX; + focalY.value = event.focalY; + }) + .onEnd(() => { + if (scale.value < 1) { + scale.value = withSpring(1); + savedScale.value = 1; + } else { + savedScale.value = scale.value; + } + }); + + const doubleTapGesture = Gesture.Tap() + .numberOfTaps(2) + .maxDuration(DOUBLE_TAP_DELAY) + .onStart((event: any) => { + if (scale.value > 1) { + // If already zoomed in, reset to normal + scale.value = withTiming(1); + savedScale.value = 1; + translateX.value = withSpring(0); + translateY.value = withSpring(0); + } else { + // Zoom in to 2x at the tap location + scale.value = withSpring(2); + savedScale.value = 2; + + // Calculate the focal point for zooming + const centerX = SCREEN_WIDTH / 2; + const centerY = SCREEN_HEIGHT / 2; + const focusX = event.x - centerX; + const focusY = event.y - centerY; + + // Adjust translation to zoom into the tapped point + translateX.value = withSpring(-focusX); + translateY.value = withSpring(-focusY); + } + }); + + const panGesture = Gesture.Pan() + .onUpdate((event: any) => { + if (scale.value > 1) { + // When zoomed in, allow panning within bounds + translateX.value = event.translationX; + translateY.value = event.translationY; + } else { + // Normal swipe behavior when not zoomed + if (Math.abs(event.translationY) > Math.abs(event.translationX)) { + translateY.value = event.translationY; + scale.value = Math.max( + 0.5, + 1 - Math.abs(event.translationY) / SCREEN_HEIGHT + ); + } + } + }) + .onEnd((event: any) => { + if (scale.value <= 1) { + if (Math.abs(event.translationY) > SCREEN_HEIGHT * 0.2) { + runOnJS(onClose)(); + } + } + + // Reset position if not zoomed + if (scale.value <= 1) { + translateX.value = 0; + translateY.value = withSpring(0); + scale.value = withSpring(1); + savedScale.value = 1; + } else { + // When zoomed, bound the pan values + const maxTranslateX = ((scale.value - 1) * SCREEN_WIDTH) / 2; + const maxTranslateY = ((scale.value - 1) * SCREEN_HEIGHT) / 2; + + translateX.value = withSpring( + Math.min(Math.max(translateX.value, -maxTranslateX), maxTranslateX) + ); + translateY.value = withSpring( + Math.min(Math.max(translateY.value, -maxTranslateY), maxTranslateY) + ); + } + }); + + const composedGesture = Gesture.Race( + doubleTapGesture, + Gesture.Simultaneous(pinchGesture, panGesture) + ); + + const animatedStyle = useAnimatedStyle(() => { + setScale(scale.value); + if (scale.value <= 1) { + } + return { + transform: [ + { translateX: translateX.value }, + { translateY: translateY.value }, + { scale: scale.value }, + ], + }; + }); + + return ( + + + ); + }); + +export default ImageViewerContent; diff --git a/packages/unstyled/image-viewer/src/ImageViewerContext.ts b/packages/unstyled/image-viewer/src/ImageViewerContext.ts new file mode 100644 index 0000000000..8bedf3e807 --- /dev/null +++ b/packages/unstyled/image-viewer/src/ImageViewerContext.ts @@ -0,0 +1,9 @@ +import React from 'react'; +import type { ImageViewerContext as ImageViewerContextType } from './types'; + +export const ImageViewerContext = React.createContext({ + onClose: () => {}, + isOpen: false, + scale: 1, + setScale: () => {}, +}); diff --git a/packages/unstyled/image-viewer/src/index.tsx b/packages/unstyled/image-viewer/src/index.tsx new file mode 100644 index 0000000000..673bd63657 --- /dev/null +++ b/packages/unstyled/image-viewer/src/index.tsx @@ -0,0 +1,47 @@ +import { default as ImageViewerMain } from './ImageViewer'; +import ImageViewerBackdrop from './ImageViewerBackdrop'; +import ImageViewerCloseButton from './ImageViewerCloseButton'; +import ImageViewerContent from './ImageViewerContent'; + +export { ImageViewerContext } from './ImageViewerContext'; +export const createImageViewer = < + ModalProps, + GestureDetectorProps, + AnimatedProps, + GestureProps, + BackdropProps, + ContentProps, + CloseButtonProps +>({ + Root, + GestureDetector, + Animated, + Gesture, + Backdrop, + Content, + CloseButton, +}: { + Root: React.ComponentType; + GestureDetector: React.ComponentType; + Animated: React.ComponentType; + Gesture: React.ComponentType; + Backdrop: React.ComponentType; + Content: React.ComponentType; + CloseButton: React.ComponentType; +}) => { + const ImageViewer: any = ImageViewerMain(Root); + ImageViewer.Backdrop = ImageViewerBackdrop(Backdrop); + ImageViewer.Content = ImageViewerContent( + Content, + GestureDetector, + Animated, + Gesture + ); + ImageViewer.CloseButton = ImageViewerCloseButton(CloseButton); + + ImageViewer.displayName = 'ImageViewer'; + ImageViewer.Backdrop.displayName = 'ImageViewer.Backdrop'; + ImageViewer.Content.displayName = 'ImageViewer.Content'; + ImageViewer.CloseButton.displayName = 'ImageViewer.CloseButton'; + return ImageViewer; +}; diff --git a/packages/unstyled/image-viewer/src/types.ts b/packages/unstyled/image-viewer/src/types.ts new file mode 100644 index 0000000000..ec2579b1d9 --- /dev/null +++ b/packages/unstyled/image-viewer/src/types.ts @@ -0,0 +1,26 @@ +export interface ImageViewerContext { + onClose: () => void; + isOpen: boolean; + scale: number; + setScale: (scale: number) => void; +} + +// export interface ImageViewerProps extends ViewProps { +// imageUrl: string; +// visible?: boolean; +// onRequestClose?: () => void; +// className?: string; +// closeButtonClassName?: string; +// closeButtonIconClassName?: string; +// imageClassName?: string; +// } + +// export type IImageViewerComponentType = { +// Modal: React.ComponentType; +// GestureHandlerRootView: React.ComponentType; +// Image: React.ComponentType; +// }; + +// export interface IImageViewer { +// Root: React.FC; +// } diff --git a/packages/unstyled/image-viewer/tsconfig.json b/packages/unstyled/image-viewer/tsconfig.json new file mode 100644 index 0000000000..c13c2a1961 --- /dev/null +++ b/packages/unstyled/image-viewer/tsconfig.json @@ -0,0 +1,31 @@ +{ + "include": ["src"], + "exclude": ["node_modules", "example"], + "paths": {}, + "compilerOptions": { + "ignoreDeprecations": "5.0", + "noEmit": false, + "declaration": true, + "allowJs": true, + "allowUnreachableCode": false, + "allowUnusedLabels": true, + "esModuleInterop": true, + "verbatimModuleSyntax": true, + "forceConsistentCasingInFileNames": true, + "jsx": "preserve", + "lib": ["esnext", "dom"], + "module": "esnext", + "moduleResolution": "node", + "noFallthroughCasesInSwitch": true, + "noImplicitReturns": true, + "noImplicitUseStrict": false, + "noStrictGenericChecks": false, + "noUnusedLocals": false, + "noUnusedParameters": true, + "resolveJsonModule": true, + "skipLibCheck": true, + "strict": true, + "target": "esnext", + "outDir": "./lib" + } +} diff --git a/yarn.lock b/yarn.lock index 257bd45361..b8c795de61 100644 --- a/yarn.lock +++ b/yarn.lock @@ -9753,7 +9753,7 @@ debug@2.6.9, debug@^2.2.0, debug@^2.3.3, debug@^2.6.0: dependencies: ms "2.0.0" -debug@4, debug@^4.1.0, debug@^4.1.1, debug@^4.3.1, debug@^4.3.2, debug@^4.3.4, debug@^4.3.7: +debug@4, debug@^4.1.0, debug@^4.1.1, debug@^4.3.1, debug@^4.3.2, debug@^4.3.4: version "4.3.7" resolved "https://registry.yarnpkg.com/debug/-/debug-4.3.7.tgz#87945b4151a011d76d95a198d7111c865c360a52" integrity sha512-Er2nc/H7RrMXZBFCEim6TCmMk02Z8vLC2Rbi1KEBggpo0fS6l0S1nnapwmIi3yW/+GOJap1Krg4w0Hg80oCqgQ== @@ -9774,6 +9774,13 @@ debug@^3.0.0, debug@^3.1.0, debug@^3.2.5, debug@^3.2.7: dependencies: ms "^2.1.1" +debug@^4.3.7: + version "4.4.0" + resolved "https://registry.yarnpkg.com/debug/-/debug-4.4.0.tgz#2b3f2aea2ffeb776477460267377dc8710faba8a" + integrity sha512-6WTZ/IxCY/T6BALoZHaE4ctp9xm+Z5kY/pzYaCHRFeyVhojxlrm+46y68HA6hr0TcwEssoxNiDEUJQjfPZ/RYA== + dependencies: + ms "^2.1.3" + decamelize-keys@^1.1.0: version "1.1.1" resolved "https://registry.yarnpkg.com/decamelize-keys/-/decamelize-keys-1.1.1.tgz#04a2d523b2f18d80d0158a43b895d56dff8d19d8" @@ -14769,73 +14776,73 @@ levn@~0.3.0: prelude-ls "~1.1.2" type-check "~0.3.2" -lightningcss-darwin-arm64@1.28.1: - version "1.28.1" - resolved "https://registry.yarnpkg.com/lightningcss-darwin-arm64/-/lightningcss-darwin-arm64-1.28.1.tgz#043c30e2d22ee68beb7f8782e96390821ba8ab34" - integrity sha512-VG3vvzM0m/rguCdm76DdobNeNJnHK+jWcdkNLFWHLh9YCotRvbRIt45JxwcHlIF8TDqWStVLTdghq5NaigVCBQ== - -lightningcss-darwin-x64@1.28.1: - version "1.28.1" - resolved "https://registry.yarnpkg.com/lightningcss-darwin-x64/-/lightningcss-darwin-x64-1.28.1.tgz#c0f975759af364699fdbd7a4756ac66767ed9767" - integrity sha512-O7ORdislvKfMohFl4Iq7fxKqdJOuuxArcglVI3amuFO5DJ0wfV3Gxgi1JRo49slfr7OVzJQEHLG4muTWYM5cTQ== - -lightningcss-freebsd-x64@1.28.1: - version "1.28.1" - resolved "https://registry.yarnpkg.com/lightningcss-freebsd-x64/-/lightningcss-freebsd-x64-1.28.1.tgz#f8eb8b63845a88d32eed71a594cf224f6c7ea4fd" - integrity sha512-b7sF89B31kYYijxVcFO7l5u6UNA862YstNu+3YbLl/IQKzveL4a5cwR5cdpG+OOhErg/c2u9WCmzZoX2I5GBvw== - -lightningcss-linux-arm-gnueabihf@1.28.1: - version "1.28.1" - resolved "https://registry.yarnpkg.com/lightningcss-linux-arm-gnueabihf/-/lightningcss-linux-arm-gnueabihf-1.28.1.tgz#726dfdb2db6ba3a7bb2169e5724d826cb585a76d" - integrity sha512-p61kXwvhUDLLzkWHjzSFfUBW/F0iy3jr3CWi3k8SKULtJEsJXTI9DqRm9EixxMSe2AMBQBt4auTYiQL4B1N51A== - -lightningcss-linux-arm64-gnu@1.28.1: - version "1.28.1" - resolved "https://registry.yarnpkg.com/lightningcss-linux-arm64-gnu/-/lightningcss-linux-arm64-gnu-1.28.1.tgz#9f4e4450617230ea557abb5ffd5d26b2047e9b62" - integrity sha512-iO+fN9hOMmzfwqcG2/BgUtMKD48H2JO/SXU44fyIwpY2veb65QF5xiRrQ9l1FwIxbGK3231KBYCtAqv+xf+NsQ== - -lightningcss-linux-arm64-musl@1.28.1: - version "1.28.1" - resolved "https://registry.yarnpkg.com/lightningcss-linux-arm64-musl/-/lightningcss-linux-arm64-musl-1.28.1.tgz#9d2561d8a5ecfb3f1f18651da0acc592e837ea3a" - integrity sha512-dnMHeXEmCUzHHZjaDpQBYuBKcN9nPC3nPFKl70bcj5Bkn5EmkcgEqm5p035LKOgvAwk1XwLpQCML6pXmCwz0NQ== - -lightningcss-linux-x64-gnu@1.28.1: - version "1.28.1" - resolved "https://registry.yarnpkg.com/lightningcss-linux-x64-gnu/-/lightningcss-linux-x64-gnu-1.28.1.tgz#91d0a41d6dd40d8965cb6c1fbd4d40e6b3460384" - integrity sha512-7vWDISaMUn+oo2TwRdf2hl/BLdPxvywv9JKEqNZB/0K7bXwV4XE9wN/C2sAp1gGuh6QBA8lpjF4JIPt3HNlCHA== - -lightningcss-linux-x64-musl@1.28.1: - version "1.28.1" - resolved "https://registry.yarnpkg.com/lightningcss-linux-x64-musl/-/lightningcss-linux-x64-musl-1.28.1.tgz#f1a9d0cafc1eb7ec72ef4f2a3a81b5631060c461" - integrity sha512-IHCu9tVGP+x5BCpA2rF3D04DBokcBza/a8AuHQU+1AiMKubuMegPwcL7RatBgK4ztFHeYnnD5NdhwhRfYMAtNA== - -lightningcss-win32-arm64-msvc@1.28.1: - version "1.28.1" - resolved "https://registry.yarnpkg.com/lightningcss-win32-arm64-msvc/-/lightningcss-win32-arm64-msvc-1.28.1.tgz#c21f7683648a9e4d856737fc22c3eca908c773b6" - integrity sha512-Erm72kHmMg/3h350PTseskz+eEGBM17Fuu79WW2Qqt0BfWSF1jHHc12lkJCWMYl5jcBHPs5yZdgNHtJ7IJS3Uw== - -lightningcss-win32-x64-msvc@1.28.1: - version "1.28.1" - resolved "https://registry.yarnpkg.com/lightningcss-win32-x64-msvc/-/lightningcss-win32-x64-msvc-1.28.1.tgz#7afe4f4128bf6b75a570e8585d287040243f7881" - integrity sha512-ZPQtvx+uQBzrSdHH8p4H3M9Alue+x369TPZAA3b4K3d92FPhpZCuBG04+HQzspam9sVeID9mI6f3VRAs2ezaEA== +lightningcss-darwin-arm64@1.28.2: + version "1.28.2" + resolved "https://registry.yarnpkg.com/lightningcss-darwin-arm64/-/lightningcss-darwin-arm64-1.28.2.tgz#a906fd84cb43d753cb5db9c367f8f38482e8fb03" + integrity sha512-/8cPSqZiusHSS+WQz0W4NuaqFjquys1x+NsdN/XOHb+idGHJSoJ7SoQTVl3DZuAgtPZwFZgRfb/vd1oi8uX6+g== + +lightningcss-darwin-x64@1.28.2: + version "1.28.2" + resolved "https://registry.yarnpkg.com/lightningcss-darwin-x64/-/lightningcss-darwin-x64-1.28.2.tgz#6c43249d4ae821416d0d78403eae56111d0c6a94" + integrity sha512-R7sFrXlgKjvoEG8umpVt/yutjxOL0z8KWf0bfPT3cYMOW4470xu5qSHpFdIOpRWwl3FKNMUdbKtMUjYt0h2j4g== + +lightningcss-freebsd-x64@1.28.2: + version "1.28.2" + resolved "https://registry.yarnpkg.com/lightningcss-freebsd-x64/-/lightningcss-freebsd-x64-1.28.2.tgz#804bc6652c6721e94a92e7bbb5e65165376cf108" + integrity sha512-l2qrCT+x7crAY+lMIxtgvV10R8VurzHAoUZJaVFSlHrN8kRLTvEg9ObojIDIexqWJQvJcVVV3vfzsEynpiuvgA== + +lightningcss-linux-arm-gnueabihf@1.28.2: + version "1.28.2" + resolved "https://registry.yarnpkg.com/lightningcss-linux-arm-gnueabihf/-/lightningcss-linux-arm-gnueabihf-1.28.2.tgz#c32595127b565690d854c9ff641831e4ad739ee1" + integrity sha512-DKMzpICBEKnL53X14rF7hFDu8KKALUJtcKdFUCW5YOlGSiwRSgVoRjM97wUm/E0NMPkzrTi/rxfvt7ruNK8meg== + +lightningcss-linux-arm64-gnu@1.28.2: + version "1.28.2" + resolved "https://registry.yarnpkg.com/lightningcss-linux-arm64-gnu/-/lightningcss-linux-arm64-gnu-1.28.2.tgz#85646f08c5efbfd7c94f8e5ed6392d5cf95fa42c" + integrity sha512-nhfjYkfymWZSxdtTNMWyhFk2ImUm0X7NAgJWFwnsYPOfmtWQEapzG/DXZTfEfMjSzERNUNJoQjPAbdqgB+sjiw== + +lightningcss-linux-arm64-musl@1.28.2: + version "1.28.2" + resolved "https://registry.yarnpkg.com/lightningcss-linux-arm64-musl/-/lightningcss-linux-arm64-musl-1.28.2.tgz#4d9bc20cf6de28c4d0c586d81c577891555ad831" + integrity sha512-1SPG1ZTNnphWvAv8RVOymlZ8BDtAg69Hbo7n4QxARvkFVCJAt0cgjAw1Fox0WEhf4PwnyoOBaVH0Z5YNgzt4dA== + +lightningcss-linux-x64-gnu@1.28.2: + version "1.28.2" + resolved "https://registry.yarnpkg.com/lightningcss-linux-x64-gnu/-/lightningcss-linux-x64-gnu-1.28.2.tgz#74bd797d7157817c4e42ec45f1844a69636a9d82" + integrity sha512-ZhQy0FcO//INWUdo/iEdbefntTdpPVQ0XJwwtdbBuMQe+uxqZoytm9M+iqR9O5noWFaxK+nbS2iR/I80Q2Ofpg== + +lightningcss-linux-x64-musl@1.28.2: + version "1.28.2" + resolved "https://registry.yarnpkg.com/lightningcss-linux-x64-musl/-/lightningcss-linux-x64-musl-1.28.2.tgz#13ce6db4c491ebbb93099d6427746ab7bff3774f" + integrity sha512-alb/j1NMrgQmSFyzTbN1/pvMPM+gdDw7YBuQ5VSgcFDypN3Ah0BzC2dTZbzwzaMdUVDszX6zH5MzjfVN1oGuww== + +lightningcss-win32-arm64-msvc@1.28.2: + version "1.28.2" + resolved "https://registry.yarnpkg.com/lightningcss-win32-arm64-msvc/-/lightningcss-win32-arm64-msvc-1.28.2.tgz#eaae12c4a58a545a3adf40b22ba9625e5c0ebd29" + integrity sha512-WnwcjcBeAt0jGdjlgbT9ANf30pF0C/QMb1XnLnH272DQU8QXh+kmpi24R55wmWBwaTtNAETZ+m35ohyeMiNt+g== + +lightningcss-win32-x64-msvc@1.28.2: + version "1.28.2" + resolved "https://registry.yarnpkg.com/lightningcss-win32-x64-msvc/-/lightningcss-win32-x64-msvc-1.28.2.tgz#1f7c4474b2dc3dd1c12e22de32e4de23bdfa41e7" + integrity sha512-3piBifyT3avz22o6mDKywQC/OisH2yDK+caHWkiMsF82i3m5wDBadyCjlCQ5VNgzYkxrWZgiaxHDdd5uxsi0/A== lightningcss@^1.27.0: - version "1.28.1" - resolved "https://registry.yarnpkg.com/lightningcss/-/lightningcss-1.28.1.tgz#311b44052e4dcb17e31929a584a9a68864a456ed" - integrity sha512-KRDkHlLlNj3DWh79CDt93fPlRJh2W1AuHV0ZSZAMMuN7lqlsZTV5842idfS1urWG8q9tc17velp1gCXhY7sLnQ== + version "1.28.2" + resolved "https://registry.yarnpkg.com/lightningcss/-/lightningcss-1.28.2.tgz#cc26fad9ad64a621bd39ac6248095891cf584cce" + integrity sha512-ePLRrbt3fgjXI5VFZOLbvkLD5ZRuxGKm+wJ3ujCqBtL3NanDHPo/5zicR5uEKAPiIjBYF99BM4K4okvMznjkVA== dependencies: detect-libc "^1.0.3" optionalDependencies: - lightningcss-darwin-arm64 "1.28.1" - lightningcss-darwin-x64 "1.28.1" - lightningcss-freebsd-x64 "1.28.1" - lightningcss-linux-arm-gnueabihf "1.28.1" - lightningcss-linux-arm64-gnu "1.28.1" - lightningcss-linux-arm64-musl "1.28.1" - lightningcss-linux-x64-gnu "1.28.1" - lightningcss-linux-x64-musl "1.28.1" - lightningcss-win32-arm64-msvc "1.28.1" - lightningcss-win32-x64-msvc "1.28.1" + lightningcss-darwin-arm64 "1.28.2" + lightningcss-darwin-x64 "1.28.2" + lightningcss-freebsd-x64 "1.28.2" + lightningcss-linux-arm-gnueabihf "1.28.2" + lightningcss-linux-arm64-gnu "1.28.2" + lightningcss-linux-arm64-musl "1.28.2" + lightningcss-linux-x64-gnu "1.28.2" + lightningcss-linux-x64-musl "1.28.2" + lightningcss-win32-arm64-msvc "1.28.2" + lightningcss-win32-x64-msvc "1.28.2" lilconfig@2.1.0, lilconfig@^2.1.0: version "2.1.0" @@ -18363,15 +18370,14 @@ react-native-gesture-handler@^2.12.1: invariant "^2.2.4" prop-types "^15.7.2" -react-native-gesture-handler@~2.14.0: - version "2.14.1" - resolved "https://registry.yarnpkg.com/react-native-gesture-handler/-/react-native-gesture-handler-2.14.1.tgz#930640231024b7921435ab476aa501dd4a6b2e01" - integrity sha512-YiM1BApV4aKeuwsM6O4C2ufwewYEKk6VMXOt0YqEZFMwABBFWhXLySFZYjBSNRU2USGppJbfHP1q1DfFQpKhdA== +react-native-gesture-handler@^2.21.2: + version "2.21.2" + resolved "https://registry.yarnpkg.com/react-native-gesture-handler/-/react-native-gesture-handler-2.21.2.tgz#824a098d7397212fbe51aa3a9df84833a4ea1c3f" + integrity sha512-HcwB225K9aeZ8e/B8nFzEh+2T4EPWTeamO1l/y3PcQ9cyCDYO2zja/G31ITpYRIqkip7XzGs6wI/gnHOQn1LDQ== dependencies: "@egjs/hammerjs" "^2.0.17" hoist-non-react-statics "^3.3.0" invariant "^2.2.4" - lodash "^4.17.21" prop-types "^15.7.2" react-native-modal-datetime-picker@^14.0.0: @@ -20103,7 +20109,16 @@ string-natural-compare@^3.0.1: resolved "https://registry.yarnpkg.com/string-natural-compare/-/string-natural-compare-3.0.1.tgz#7a42d58474454963759e8e8b7ae63d71c1e7fdf4" integrity sha512-n3sPwynL1nwKi3WJ6AIsClwBMa0zTi54fn2oLU6ndfTSIO05xaznjSf15PcBZU6FNWbmN5Q6cxT4V5hGvB4taw== -"string-width-cjs@npm:string-width@^4.2.0", "string-width@^1.0.2 || 2 || 3 || 4", string-width@^4.0.0, string-width@^4.1.0, string-width@^4.2.0, string-width@^4.2.2, string-width@^4.2.3: +"string-width-cjs@npm:string-width@^4.2.0": + version "4.2.3" + resolved "https://registry.yarnpkg.com/string-width/-/string-width-4.2.3.tgz#269c7117d27b05ad2e536830a8ec895ef9c6d010" + integrity sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g== + dependencies: + emoji-regex "^8.0.0" + is-fullwidth-code-point "^3.0.0" + strip-ansi "^6.0.1" + +"string-width@^1.0.2 || 2 || 3 || 4", string-width@^4.0.0, string-width@^4.1.0, string-width@^4.2.0, string-width@^4.2.2, string-width@^4.2.3: version "4.2.3" resolved "https://registry.yarnpkg.com/string-width/-/string-width-4.2.3.tgz#269c7117d27b05ad2e536830a8ec895ef9c6d010" integrity sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g== @@ -20218,7 +20233,7 @@ string_decoder@~1.1.1: dependencies: safe-buffer "~5.1.0" -"strip-ansi-cjs@npm:strip-ansi@^6.0.1", strip-ansi@^6.0.0, strip-ansi@^6.0.1: +"strip-ansi-cjs@npm:strip-ansi@^6.0.1": version "6.0.1" resolved "https://registry.yarnpkg.com/strip-ansi/-/strip-ansi-6.0.1.tgz#9e26c63d30f53443e9489495b2105d37b67a85d9" integrity sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A== @@ -20246,6 +20261,13 @@ strip-ansi@^5.0.0, strip-ansi@^5.1.0, strip-ansi@^5.2.0: dependencies: ansi-regex "^4.1.0" +strip-ansi@^6.0.0, strip-ansi@^6.0.1: + version "6.0.1" + resolved "https://registry.yarnpkg.com/strip-ansi/-/strip-ansi-6.0.1.tgz#9e26c63d30f53443e9489495b2105d37b67a85d9" + integrity sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A== + dependencies: + ansi-regex "^5.0.1" + strip-ansi@^7.0.1: version "7.1.0" resolved "https://registry.yarnpkg.com/strip-ansi/-/strip-ansi-7.1.0.tgz#d5b6568ca689d8561370b0707685d22434faff45" @@ -22245,7 +22267,7 @@ worker-rpc@^0.1.0: dependencies: microevent.ts "~0.1.1" -"wrap-ansi-cjs@npm:wrap-ansi@^7.0.0", wrap-ansi@^7.0.0: +"wrap-ansi-cjs@npm:wrap-ansi@^7.0.0": version "7.0.0" resolved "https://registry.yarnpkg.com/wrap-ansi/-/wrap-ansi-7.0.0.tgz#67e145cff510a6a6984bdf1152911d69d2eb9e43" integrity sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q== @@ -22272,6 +22294,15 @@ wrap-ansi@^6.0.1, wrap-ansi@^6.2.0: string-width "^4.1.0" strip-ansi "^6.0.0" +wrap-ansi@^7.0.0: + version "7.0.0" + resolved "https://registry.yarnpkg.com/wrap-ansi/-/wrap-ansi-7.0.0.tgz#67e145cff510a6a6984bdf1152911d69d2eb9e43" + integrity sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q== + dependencies: + ansi-styles "^4.0.0" + string-width "^4.1.0" + strip-ansi "^6.0.0" + wrap-ansi@^8.0.1, wrap-ansi@^8.1.0: version "8.1.0" resolved "https://registry.yarnpkg.com/wrap-ansi/-/wrap-ansi-8.1.0.tgz#56dc22368ee570face1b49819975d9b9a5ead214" From 01276c772198a1240ab6a6add6c904aa713cd5b8 Mon Sep 17 00:00:00 2001 From: Sanchitv3 Date: Fri, 3 Jan 2025 18:28:11 +0530 Subject: [PATCH 2/5] chore: updated stories and readme --- .../ImageViewer/ImageViewer.stories.tsx | 6 +- .../components/ImageViewer/ImageViewer.tsx | 25 +++-- .../ImageViewer/index.nw.stories.mdx | 36 ++++--- .../nativewind/image-viewer/index.tsx | 102 +++++++++++------- packages/unstyled/image-viewer/README.md | 65 ++++++++--- packages/unstyled/image-viewer/src/index.tsx | 8 +- packages/unstyled/image-viewer/src/types.ts | 60 ++++++++--- 7 files changed, 205 insertions(+), 97 deletions(-) diff --git a/example/storybook-nativewind/src/components/ImageViewer/ImageViewer.stories.tsx b/example/storybook-nativewind/src/components/ImageViewer/ImageViewer.stories.tsx index 7a4016ac0e..b672234484 100644 --- a/example/storybook-nativewind/src/components/ImageViewer/ImageViewer.stories.tsx +++ b/example/storybook-nativewind/src/components/ImageViewer/ImageViewer.stories.tsx @@ -7,10 +7,12 @@ const ImageViewerMeta: ComponentMeta = { // metaInfo is required for figma generation // @ts-ignore metaInfo: { - componentDescription: `With the Image component, you can enhance the look and feel of your app or website by incorporating compelling imagery.`, //change description + componentDescription: `The ImageViewer component provides a modal view for displaying and interacting with images, supporting features like pinch-to-zoom, double-tap zoom, and swipe-to-dismiss.`, }, argTypes: {}, - args: {}, + args: { + images: [{ id: 1, url: 'https://picsum.photos/1000/1000' }], + }, }; export default ImageViewerMeta; diff --git a/example/storybook-nativewind/src/components/ImageViewer/ImageViewer.tsx b/example/storybook-nativewind/src/components/ImageViewer/ImageViewer.tsx index 6ce66e87dd..83cf11d7b2 100644 --- a/example/storybook-nativewind/src/components/ImageViewer/ImageViewer.tsx +++ b/example/storybook-nativewind/src/components/ImageViewer/ImageViewer.tsx @@ -4,35 +4,42 @@ import { Image, Pressable } from 'react-native'; import { ImageViewer, ImageViewerBackdrop, + ImageViewerCloseButton, ImageViewerContent, ImageViewerImage, } from '@/components/ui/image-viewer'; +import { Icon } from '@/components/ui/icon'; +import { CloseIcon } from '@/components/ui/icon'; -const ImageViewerBasic = () => { - const Images = [ - { id: 1, url: 'https://picsum.photos/200/300' }, - { id: 2, url: 'https://picsum.photos/200/300' }, - { id: 3, url: 'https://picsum.photos/200/300' }, - ]; +const ImageViewerBasic = ({ ...props }: any) => { + const Images = [{ id: 1, url: 'https://picsum.photos/1000/1000' }]; const [visible, setVisible] = useState(false); return ( <> setVisible(true)}> - setVisible(false)}> + setVisible(false)} + {...props} + > ( )} - /> + > + + + + diff --git a/example/storybook-nativewind/src/components/ImageViewer/index.nw.stories.mdx b/example/storybook-nativewind/src/components/ImageViewer/index.nw.stories.mdx index 01339038dd..128d7cf80b 100644 --- a/example/storybook-nativewind/src/components/ImageViewer/index.nw.stories.mdx +++ b/example/storybook-nativewind/src/components/ImageViewer/index.nw.stories.mdx @@ -1,11 +1,11 @@ --- -title: gluestack-ui Image Component | Installation, Usage, and API +title: ImageViewer | gluestack-ui | Installation, Usage, and API -description: Enhance your app or website with captivating visuals using our Image feature. +description: The ImageViewer component provides a modal view for displaying and interacting with images, supporting features like pinch-to-zoom, double-tap zoom, and swipe-to-dismiss. -pageTitle: Image +pageTitle: ImageViewer -pageDescription: Enhance your app or website with captivating visuals using our Image feature. +pageDescription: The ImageViewer component provides a modal view for displaying and interacting with images, supporting features like pinch-to-zoom, double-tap zoom, and swipe-to-dismiss. showHeader: true --- @@ -17,7 +17,6 @@ import { Meta } from '@storybook/addon-docs'; import { ImageViewer } from '../../core-components/nativewind'; import { transformedCode } from '../../utils'; import Wrapper from '../../core-components/nativewind/Wrapper'; -import UnitoolsImageEg from '../../extra-components/nativewind/UnitoolsImage'; import { AppProvider, @@ -29,9 +28,8 @@ import { Tabs } from '@gluestack/design-system'; import { CollapsibleCode } from '@gluestack/design-system'; -import UnitoolsImage from '@unitools/image'; -This is an illustration of **Image** component. +This is an illustration of **ImageViewer** component. @@ -90,14 +88,14 @@ This is an illustration of **Image** component. ### Step 1: Install the following dependencies: ```bash -npm i @gluestack-ui/image +npm i @gluestack-ui/image-viewer ``` ### Step 2: Copy and paste the following code into your project. ```jsx -%%-- File: core-components/nativewind/image/index.tsx --%% +%%-- File: core-components/nativewind/image-viewer/index.tsx --%% ``` @@ -112,24 +110,32 @@ npm i @gluestack-ui/image To use this component in your project, include the following import statement in your file. ```jsx -import { Image } from '@/components/ui/image'; +import { ImageViewer, ImageViewerBackdrop, ImageViewerContent, ImageViewerCloseButton } from '@/components/ui/image-viewer'; ``` ```jsx -export default () => ; +export default () => ( + + + + + + + +); ``` ### Component Props This section provides a comprehensive reference list for the component props, detailing descriptions, properties, types, and default behavior for easy project integration. -#### Image +#### ImageViewer -It inherits all the properties of React Native's [Image](https://reactnative.dev/docs/image) component. +It inherits all the properties of React Native's [Modal](https://reactnative.dev/docs/modal) component. ### Props -Image component is created using Image component from react-native. It extends all the props supported by [React Native Image](https://reactnative.dev/docs/image#props). +ImageViewer component is created using Modal component from react-native. It extends all the props supported by [React Native Modal](https://reactnative.dev/docs/modal#props). ### Examples diff --git a/example/storybook-nativewind/src/core-components/nativewind/image-viewer/index.tsx b/example/storybook-nativewind/src/core-components/nativewind/image-viewer/index.tsx index 9c29a39a1f..4a241acc1a 100644 --- a/example/storybook-nativewind/src/core-components/nativewind/image-viewer/index.tsx +++ b/example/storybook-nativewind/src/core-components/nativewind/image-viewer/index.tsx @@ -10,7 +10,8 @@ import { } from 'react-native-gesture-handler'; import Animated from 'react-native-reanimated'; -const imageViewerStyle = tva({ +import { VariantProps } from '@gluestack-ui/nativewind-utils/types'; +const ImageViewerStyle = tva({ base: 'flex-1 bg-background-dark justify-center items-center ', }); @@ -23,7 +24,7 @@ const BackdropStyle = tva({ }); const CloseButtonStyle = tva({ - base: 'absolute top-4 right-4 z-10 bg-background-dark rounded-full w-8 h-8 justify-center items-center cursor-pointer', + base: 'absolute top-4 right-4 z-10 bg-white rounded-full w-8 h-8 justify-center items-center cursor-pointer', }); const UIImageViewer = createImageViewer({ @@ -31,59 +32,80 @@ const UIImageViewer = createImageViewer({ Backdrop: Animated.View, Content: GestureHandlerRootView, Animated: Animated.View, - Gesture: Gesture, + Gesture: Gesture as any, GestureDetector: GestureDetector, CloseButton: Pressable, }); -// type ImageViewerProps = VariantProps & -// React.ComponentProps; +type IImageViewerProps = React.ComponentProps & + VariantProps & { className?: string }; -const ImageViewer = React.forwardRef(({ className, ...props }: any, ref) => { +type IImageViewerBackdropProps = React.ComponentProps< + typeof UIImageViewer.Backdrop +> & + VariantProps & { className?: string }; + +type IImageViewerContentProps = React.ComponentProps< + typeof UIImageViewer.Content +> & + VariantProps & { className?: string }; + +type IImageViewerCloseButtonProps = React.ComponentProps< + typeof UIImageViewer.CloseButton +> & + VariantProps & { className?: string }; + +const ImageViewer = React.forwardRef< + React.ElementRef, + IImageViewerProps +>(({ className, ...props }, ref) => { return ( ); }); -const ImageViewerBackdrop = React.forwardRef( - ({ className, ...props }: any, ref) => { - return ( - - ); - } -); +const ImageViewerBackdrop = React.forwardRef< + React.ElementRef, + IImageViewerBackdropProps +>(({ className, ...props }, ref) => { + return ( + + ); +}); -const ImageViewerContent = React.forwardRef( - ({ className, ...props }: any, ref) => { - return ( - - ); - } -); +const ImageViewerContent = React.forwardRef< + React.ElementRef, + IImageViewerContentProps +>(({ className, ...props }, ref) => { + return ( + + ); +}); -const ImageViewerCloseButton = React.forwardRef( - ({ className, ...props }: any, ref) => { - return ( - - ); - } -); +const ImageViewerCloseButton = React.forwardRef< + React.ElementRef, + IImageViewerCloseButtonProps +>(({ className, ...props }, ref) => { + return ( + + ); +}); const ImageViewerImage = React.forwardRef( ({ className, ...props }: any, ref) => { diff --git a/packages/unstyled/image-viewer/README.md b/packages/unstyled/image-viewer/README.md index 7964c25888..588adbfa6b 100644 --- a/packages/unstyled/image-viewer/README.md +++ b/packages/unstyled/image-viewer/README.md @@ -15,36 +15,75 @@ $ npm i @gluestack-ui/image-viewer ## Usage -A image component is a graphical user interface element that enables users to act by clicking or tapping. It can be customized in size, shape, color, and behavior to fit the design of the application or website. Here's an example how to use this package to create one: +The ImageViewer component provides a modal view for displaying and interacting with images, supporting features like pinch-to-zoom, double-tap zoom, and swipe-to-dismiss. Here's an example of how to use this package: ```jsx -import { createImage } from '@gluestack-ui/image'; -import { Root } from './styled-components'; +import { createImageViewer } from '@gluestack-ui/image-viewer'; +import { Root, Backdrop, Content, CloseButton } from './styled-components'; -export const Image = createImage({ +export const ImageViewer = createImageViewer({ Root, + Backdrop, + Content, + CloseButton, }); ``` -## Customizing the Image: +## Customizing the ImageViewer -Default styling of all these components can be found in the components/core/image file. For reference, you can view the [source code](https://github.com/gluestack/gluestack-ui/blob/development/example/storybook/src/ui-components/Image/index.tsx) of the styled `Image` components. +Default styling of all these components can be found in the components/core/image-viewer file. For reference, you can view the [source code](https://github.com/gluestack/gluestack-ui/blob/development/example/storybook/src/ui-components/ImageViewer/index.tsx) of the styled `ImageViewer` components. ```jsx // import the styles -import { Root } from '../components/core/image/styled-components'; +import { + Root, + Backdrop, + Content, + CloseButton, +} from '../components/core/image-viewer/styled-components'; -// import the createImage function -import { createImage } from '@gluestack-ui/image'; +// import the createImageViewer function +import { createImageViewer } from '@gluestack-ui/image-viewer'; // Understanding the API -const Image = createImage({ +const ImageViewer = createImageViewer({ Root, + Backdrop, + Content, + CloseButton, }); -// Using the image component -export default () => ; +// Using the ImageViewer component +export default () => ( + + + ( + + )} + /> + + +); ``` +## Component Props + +### ImageViewer + +| Prop | Type | Default | Description | +| -------- | --------- | ------- | -------------------------------------------------- | +| isOpen | boolean | false | If true, the modal will open | +| onClose | function | - | Callback invoked when the modal is closed | +| children | ReactNode | - | The content to be rendered inside the image viewer | + +### ImageViewerContent + +| Prop | Type | Default | Description | +| ------------ | -------------------------------- | ------- | ---------------------------------- | +| images | Array<{id: number, url: string}> | - | Array of image objects to display | +| renderImages | (item: any) => ReactNode | - | Function to render each image item | + More guides on how to get started are available -[here](https://ui.gluestack.io/docs/components/forms/image). +[here](https://ui.gluestack.io/docs/components/media-and-icons/image-viewer). diff --git a/packages/unstyled/image-viewer/src/index.tsx b/packages/unstyled/image-viewer/src/index.tsx index 673bd63657..63dea62ffe 100644 --- a/packages/unstyled/image-viewer/src/index.tsx +++ b/packages/unstyled/image-viewer/src/index.tsx @@ -2,6 +2,7 @@ import { default as ImageViewerMain } from './ImageViewer'; import ImageViewerBackdrop from './ImageViewerBackdrop'; import ImageViewerCloseButton from './ImageViewerCloseButton'; import ImageViewerContent from './ImageViewerContent'; +import type { IImageViewerComponentType } from './types'; export { ImageViewerContext } from './ImageViewerContext'; export const createImageViewer = < @@ -43,5 +44,10 @@ export const createImageViewer = < ImageViewer.Backdrop.displayName = 'ImageViewer.Backdrop'; ImageViewer.Content.displayName = 'ImageViewer.Content'; ImageViewer.CloseButton.displayName = 'ImageViewer.CloseButton'; - return ImageViewer; + return ImageViewer as IImageViewerComponentType< + ModalProps, + BackdropProps, + ContentProps, + CloseButtonProps + >; }; diff --git a/packages/unstyled/image-viewer/src/types.ts b/packages/unstyled/image-viewer/src/types.ts index ec2579b1d9..46b6e72fe5 100644 --- a/packages/unstyled/image-viewer/src/types.ts +++ b/packages/unstyled/image-viewer/src/types.ts @@ -5,22 +5,48 @@ export interface ImageViewerContext { setScale: (scale: number) => void; } -// export interface ImageViewerProps extends ViewProps { -// imageUrl: string; -// visible?: boolean; -// onRequestClose?: () => void; -// className?: string; -// closeButtonClassName?: string; -// closeButtonIconClassName?: string; -// imageClassName?: string; -// } +export interface ImageViewerProps { + /** + * If true, the modal will open. Useful for controllable state behavior. + */ + isOpen?: boolean; + /** + * Callback invoked when the modal is closed. + */ + onClose?: any; + /** + * If true, the modal will be opened by default. + */ +} + +export interface ImageViewerContentProps { + images: { id: number; url: string }[]; + renderImages: (item: any) => React.ReactNode; +} -// export type IImageViewerComponentType = { -// Modal: React.ComponentType; -// GestureHandlerRootView: React.ComponentType; -// Image: React.ComponentType; -// }; +export interface ImageViewerCloseButtonProps { + onClose: () => void; +} -// export interface IImageViewer { -// Root: React.FC; -// } +export type IImageViewerComponentType< + ImageViewerProps, + ImageViewerContentProps, + ImageViewerCloseButtonProps, + ImageViewerBackdropProps +> = React.ForwardRefExoticComponent< + React.PropsWithoutRef & + React.RefAttributes +> & { + Content: React.ForwardRefExoticComponent< + React.PropsWithoutRef & + React.RefAttributes + >; + CloseButton: React.ForwardRefExoticComponent< + React.PropsWithoutRef & + React.RefAttributes + >; + Backdrop: React.ForwardRefExoticComponent< + React.PropsWithoutRef & + React.RefAttributes + >; +}; From 4bec2205960b87726170acae65fe1543a3a41b01 Mon Sep 17 00:00:00 2001 From: Sanchitv3 Date: Mon, 6 Jan 2025 16:49:14 +0530 Subject: [PATCH 3/5] fix: fixed pan gesture while zoomed in --- .../image-viewer/src/ImageViewerContent.tsx | 22 +++++++++++++------ 1 file changed, 15 insertions(+), 7 deletions(-) diff --git a/packages/unstyled/image-viewer/src/ImageViewerContent.tsx b/packages/unstyled/image-viewer/src/ImageViewerContent.tsx index e5cf7fcce5..575140115b 100644 --- a/packages/unstyled/image-viewer/src/ImageViewerContent.tsx +++ b/packages/unstyled/image-viewer/src/ImageViewerContent.tsx @@ -26,6 +26,8 @@ const ImageViewerContent = ( const translateY = useSharedValue(0); const focalX = useSharedValue(0); const focalY = useSharedValue(0); + const lastTranslateX = useSharedValue(0); + const lastTranslateY = useSharedValue(0); const pinchGesture = Gesture.Pinch() .onStart(() => { @@ -55,11 +57,11 @@ const ImageViewerContent = ( // If already zoomed in, reset to normal scale.value = withTiming(1); savedScale.value = 1; - translateX.value = withSpring(0); - translateY.value = withSpring(0); + translateX.value = withTiming(0); + translateY.value = withTiming(0); } else { // Zoom in to 2x at the tap location - scale.value = withSpring(2); + scale.value = withTiming(2); savedScale.value = 2; // Calculate the focal point for zooming @@ -69,17 +71,23 @@ const ImageViewerContent = ( const focusY = event.y - centerY; // Adjust translation to zoom into the tapped point - translateX.value = withSpring(-focusX); - translateY.value = withSpring(-focusY); + translateX.value = withTiming(-focusX); + translateY.value = withTiming(-focusY); } }); const panGesture = Gesture.Pan() + .onStart(() => { + // Store the current translation values when starting the pan + lastTranslateX.value = translateX.value; + lastTranslateY.value = translateY.value; + }) .onUpdate((event: any) => { if (scale.value > 1) { // When zoomed in, allow panning within bounds - translateX.value = event.translationX; - translateY.value = event.translationY; + // Calculate new positions based on the start position plus the new translation + translateX.value = lastTranslateX.value + event.translationX; + translateY.value = lastTranslateY.value + event.translationY; } else { // Normal swipe behavior when not zoomed if (Math.abs(event.translationY) > Math.abs(event.translationX)) { From 014882d6d8931c919a57f2db54c4719d3219fe6d Mon Sep 17 00:00:00 2001 From: Sanchitv3 Date: Mon, 6 Jan 2025 16:52:01 +0530 Subject: [PATCH 4/5] chore: added export for image-viewer in core components --- .../storybook-nativewind/src/core-components/nativewind/index.ts | 1 + 1 file changed, 1 insertion(+) diff --git a/example/storybook-nativewind/src/core-components/nativewind/index.ts b/example/storybook-nativewind/src/core-components/nativewind/index.ts index 23ee62e6aa..b82224e24e 100644 --- a/example/storybook-nativewind/src/core-components/nativewind/index.ts +++ b/example/storybook-nativewind/src/core-components/nativewind/index.ts @@ -51,3 +51,4 @@ export * from './image-background'; export * from './skeleton'; export * from './bottomsheet'; export * from './drawer'; +export * from './image-viewer'; From 2f9e8f86e289e6131cd93fd8cce9a6e4975d76de Mon Sep 17 00:00:00 2001 From: Sanchitv3 Date: Mon, 6 Jan 2025 16:52:53 +0530 Subject: [PATCH 5/5] chore: updated stories.mdx for image-viewer --- .../ImageViewer/index.nw.stories.mdx | 283 ++++++++++++++---- .../nativewind/image-viewer/index.tsx | 6 +- 2 files changed, 236 insertions(+), 53 deletions(-) diff --git a/example/storybook-nativewind/src/components/ImageViewer/index.nw.stories.mdx b/example/storybook-nativewind/src/components/ImageViewer/index.nw.stories.mdx index 128d7cf80b..dc172cb97b 100644 --- a/example/storybook-nativewind/src/components/ImageViewer/index.nw.stories.mdx +++ b/example/storybook-nativewind/src/components/ImageViewer/index.nw.stories.mdx @@ -1,11 +1,11 @@ --- title: ImageViewer | gluestack-ui | Installation, Usage, and API -description: The ImageViewer component provides a modal view for displaying and interacting with images, supporting features like pinch-to-zoom, double-tap zoom, and swipe-to-dismiss. +description: The ImageViewer component provides a modal view for displaying and interacting with images, supporting features like pinch-to-zoom, double-tap zoom, and swipe up/down to dismiss. pageTitle: ImageViewer -pageDescription: The ImageViewer component provides a modal view for displaying and interacting with images, supporting features like pinch-to-zoom, double-tap zoom, and swipe-to-dismiss. +pageDescription: The ImageViewer component provides a modal view for displaying and interacting with images, supporting features like pinch-to-zoom, double-tap zoom, and swipe up/down to dismiss. showHeader: true --- @@ -14,19 +14,31 @@ import { Meta } from '@storybook/addon-docs'; -import { ImageViewer } from '../../core-components/nativewind'; +import { + ImageViewer, + ImageViewerBackdrop, + ImageViewerCloseButton, + ImageViewerContent, + ImageViewerImage, + CloseIcon, + Icon, + Center, + Pressable, + Image +} from '../../core-components/nativewind'; import { transformedCode } from '../../utils'; -import Wrapper from '../../core-components/nativewind/Wrapper'; +import { useState } from 'react'; import { - AppProvider, CodePreview, - Text, Table, TableContainer, + AddIcon, + InfoIcon, InlineCode, Tabs } from '@gluestack/design-system'; +import Wrapper from '../../core-components/nativewind/Wrapper'; import { CollapsibleCode } from '@gluestack/design-system'; This is an illustration of **ImageViewer** component. @@ -41,21 +53,49 @@ This is an illustration of **ImageViewer** component. showArgsController={true} metaData={{ code: ` + function App() { + const [visible, setVisible] = React.useState(false); + const Images = [{ id: 1, url: 'https://picsum.photos/1000/1000' }]; + return ( +
+ setVisible(true)}> + + + setVisible(false)}> + + ()} > + + + + + + +
+ ); + } `, transformCode: (code) => { - return transformedCode(code); + return transformedCode(code, 'function', 'App'); }, scope: { Wrapper, ImageViewer, + ImageViewerBackdrop, + ImageViewerContent, + ImageViewerCloseButton, + ImageViewerImage, + CloseIcon, + Icon, + useState, + Pressable, + Image, + Center, }, - argsType: { - size: { - control: 'select', - options: ['2xs', 'xs', 'sm', 'md', 'lg', 'xl', '2xl'], - default: 'md', - }, - }, + argsType: {}, }} />
@@ -110,7 +150,7 @@ npm i @gluestack-ui/image-viewer To use this component in your project, include the following import statement in your file. ```jsx -import { ImageViewer, ImageViewerBackdrop, ImageViewerContent, ImageViewerCloseButton } from '@/components/ui/image-viewer'; +import { ImageViewer, ImageViewerBackdrop, ImageViewerContent, ImageViewerCloseButton, ImageViewerImage } from '@/components/ui/image-viewer'; ``` ```jsx @@ -118,7 +158,8 @@ export default () => ( - + + @@ -130,51 +171,189 @@ export default () => ( This section provides a comprehensive reference list for the component props, detailing descriptions, properties, types, and default behavior for easy project integration. #### ImageViewer +The`ImageViewer` component serves as the main container for displaying images in a modal view. It provides a user-friendly interface for viewing images with features like pinch-to-zoom, double-tap zoom, and swipe gestures for navigation and dismissal. It is built on top of React Native's [Modal](https://reactnative.dev/docs/modal) component, inheriting all its properties and behaviors. -It inherits all the properties of React Native's [Modal](https://reactnative.dev/docs/modal) component. + + + + + + + Prop + + + Type + + + Default + + + Description + + + Required + + + + + + + + isOpen + + + + boolean + + + - + + + {`If true, the image-viewer modal will open. Useful for controllable state behavior.`} + + + Yes + + + + + + onClose + + + + {`() => any`} + + + - + + + {`Callback invoked when the image-viewer modal is closed.`} + + + Yes + + + +
+
+
-### Props +#### ImageViewerContent -ImageViewer component is created using Modal component from react-native. It extends all the props supported by [React Native Modal](https://reactnative.dev/docs/modal#props). +The `ImageViewerContent` component is responsible for rendering the images within the `ImageViewer`. It supports gestures for zooming and panning, allowing users to interact with the images. This component leverages React Native's [Animated](https://reactnative.dev/docs/animated#props) & [View](https://reactnative.dev/docs/view) components, as well as gesture handling from the [react-native-gesture-handler](https://docs.swmansion.com/react-native-gesture-handler/docs/) library. -### Examples + + + + + + + Prop + + + Type + + + Default + + + Description + + + Required + + + + + + + + images + + + + Array<{`{id: number, url: string}`}> + + + - + + + Array of image objects to display + + + Yes + + + + + + renderImages + + + + (item: any) => ReactNode + + + - + + + Function to render each image item + + + Yes + + + +
+
+
-The Examples section offers visual previews of the component, letting you quickly identify the best fit for your needs. Just copy the code and use it in your project. +#### ImageViewerCloseButton -#### Basic Unitools Image +The `ImageViewerCloseButton` component provides a customizable button for closing the `ImageViewer`. It is typically placed within the `ImageViewerContent` and can be styled to match the application's design. It inherits properties from React Native's [View](https://reactnative.dev/docs/view) component. -The below example will run for both Expo and Next.js projects. For installation steps, refer to the [Installation](https://unitools.geekyants.com/packages/image/) section of ```@unitools/image```. +#### ImageViewerBackdrop + +The `ImageViewerBackdrop` component serves as the background layer of the `ImageViewer`, providing a dimmed or blurred effect behind the content. It enhances the focus on the images being viewed. This component is built using React Native's [Animated](https://reactnative.dev/docs/animated#props) & [View](https://reactnative.dev/docs/view) components, allowing for smooth transitions and animations. + +#### ImageViewerImage + +The `ImageViewerImage` component is used to display individual images within the `ImageViewerContent`. It supports all the properties of React Native's [Image](https://reactnative.dev/docs/image) component, making it easy to customize the appearance and behavior of the images. + +### Example + +The Example section offers visual previews of the component, letting you quickly identify the best fit for your needs. Just copy the code and use it in your project. ```jsx -import Image from '@unitools/image'; +import { ImageViewer, ImageViewerBackdrop, ImageViewerContent, ImageViewerCloseButton, ImageViewerImage } from '@/components/ui/image-viewer'; +import { CloseIcon, Icon } from '@/components/ui/icon'; +import React, { useState } from 'react'; ``` - +```jsx + function App() { + const [visible, setVisible] = React.useState(false); + const Images = [{ id: 1, url: 'https://picsum.photos/1000/1000' }]; + return ( + <> + {setVisible(false)}}> + + ()} > + + + + + + + + // For Showing the Image that can be clicked to open the ImageViewer + setVisible(true)}> + + + +); +} +``` +> Note: Currently, the ImageViewer component is limited to only single image, multiple image support will be added in the future. - - - `, - transformCode: (code) => { - return transformedCode(code); - }, - scope: { - Wrapper, - UnitoolsImage:Image, - }, - argsType: { - }, - }} - /> - \ No newline at end of file diff --git a/example/storybook-nativewind/src/core-components/nativewind/image-viewer/index.tsx b/example/storybook-nativewind/src/core-components/nativewind/image-viewer/index.tsx index 4a241acc1a..ff0f083a66 100644 --- a/example/storybook-nativewind/src/core-components/nativewind/image-viewer/index.tsx +++ b/example/storybook-nativewind/src/core-components/nativewind/image-viewer/index.tsx @@ -48,7 +48,11 @@ type IImageViewerBackdropProps = React.ComponentProps< type IImageViewerContentProps = React.ComponentProps< typeof UIImageViewer.Content > & - VariantProps & { className?: string }; + VariantProps & { + className?: string; + images: { id: number; url: string }[]; + renderImages: (item: any) => React.ReactNode; + }; type IImageViewerCloseButtonProps = React.ComponentProps< typeof UIImageViewer.CloseButton