Galeria

repository·master·Indexed 21 days ago

https://github.com/nandorojo/galeria

A high-performance image viewer for React and React Native featuring shared element transitions, pinch-to-zoom, and multi-image support. It utilizes a 'Bring Your Own Image Component' (BYOIC™) pattern, allowing compatibility with react-native, expo-image, next/image, solito/image, and standard HTML <img> tags. Requires New Architecture (Fabric), Expo SDK 54+ or React Native 0.79+, and iOS 16.4+.

Tokens
4.4K
Snippets
18
Records
20
Agent score
73%

What's inside @nandorojo/galeria

  1. How Galeria works (BYOIC™)

    master
    Galeria follows a 'Bring Your Own Image Component' (BYOIC™) pattern. It provides the orchestration logic (transitions, gestures, and state), while you provide the actual image component. This allows it to work seamlessly with any library including react-native, expo-image, next/image, solito/image, or standard HTML <img> tags on the web.
  2. Install @nandorojo/galeria

    master

    Install the package using your preferred package manager.

    Requirements:

    • New Architecture (Fabric): Required. This means Expo SDK 54+ or React Native 0.79+.
    • iOS 16.4+: Ensure your deployment target is set to iOS 16.4 or higher.

    Platform Specific Setup:

    • Next.js / Solito: Add @nandorojo/galeria to transpilePackages in your next.config.js.
    • Expo: Galeria requires a development client (it does not work with Expo Go). After installation, run npx expo prebuild and then npx expo run:ios or npx expo run:android to rebuild native code.
    yarn add @nandorojo/galeria
    
    # or
    
    npm i @nandorojo/galeria
  3. The structure of GaleriaContext

    master

    The GaleriaContext object defines the internal state and configuration for the Galeria component. While typically managed internally, understanding its shape is useful for developers extending the library or interacting with the context.

    Key properties include:

    • initialIndex: The starting image index.
    • open: Boolean indicating if the gallery is visible.
    • urls: An array of ImageSource (strings or React Native asset sources).
    • theme: Either 'dark' or 'light'.
    • src: The source string for the current image.
    • hideBlurOverlay: Boolean to toggle the blur overlay.
    • hidePageIndicators: Boolean to toggle page indicators.
    • setOpen: A function used to transition the gallery state. It accepts an object with { open: true, src: string, initialIndex: number, id?: string } to open the gallery, or { open: false } to close it.
    export const GaleriaContext = createContext({
      initialIndex: 0,
      open: false,
      urls: [] as unknown as undefined | ImageSource[],
      closeIconName: undefined as undefined | SFSymbol,
      setOpen: (
        info:
          | { open: true; src: string; initialIndex: number; id?: string }
          | { open: false }
      ) => {},
      theme: 'dark' as 'dark' | 'light',
      src: '',
      hideBlurOverlay: false,
      hidePageIndicators: false,
    })
  4. Use Galeria on Web with standard HTML tags

    master

    On the web, Galeria is a pure React component library that uses Framer Motion and does not rely on React Native code. You can use standard HTML <img> tags as your image component.

    Note: Web support currently only supports viewing a single image at a time.

    import { Galeria } from '@nandorojo/galeria'
    
    const urls = ['https://my-image.com/image.jpg']
    
    export const WebSupport = () => (
      <Galeria urls={urls}>
        <Galeria.Image>
          <img src={urls[0]} width={100} height={100} />
        </Galeria.Image>
      </Galeria>
    )
  5. Use Galeria for multiple images

    master

    For multiple images, pass an array of URLs to the urls prop. You must map through the URLs and provide a Galeria.Image component for each, passing the corresponding index to ensure correct synchronization.

    import { Galeria } from '@nandorojo/galeria'
    import { Image } from 'react-native'
    
    import localImage from './assets/local-image.png'
    
    const urls = ['https://my-image.com/image.jpg', localImage]
    
    export const MutliImage = ({ style }) => (
      <Galeria urls={urls}>
        {urls.map((url, index) => (
           <Galeria.Image index={index} key={...}>
             <Image source={typeof url === 'string' ? { uri: url } : url} style={style} />
           </Galeria.Image>
         ))}
      </Galeria>
    )
  6. Integrate Galeria with FlashList

    master

    Galeria is compatible with @shopify/flash-list. When using it within a list, wrap the item component inside Galeria.Image and pass the item's index to the Galeria.Image component.

    Note: It is recommended to memoize the component returned by renderItem for performance.

    import { Galeria } from '@nandorojo/galeria'
    import { Image } from 'react-native'
    import { FlashList } from '@shopify/flash-list'
    
    const urls = ['https://my-image.com/image.jpg', localImage]
    const size = 100
    
    export const FlashListSupport = () => {
      return (
        <Galeria urls={urls}>
          <FlashList
            data={urls}
            renderItem={({ item, index }) => {
              return (
                <Galeria.Image index={index}>
                  <Image
                    source={src(item)}
                    style={{ width: size, height: size }}
                  />
                </Galeria.Image>
              )
            }}
            numColumns={3}
            estimatedItemSize={size}
            keyExtractor={(item, i) => item + i}
          />
        </Galeria>
      )
    }
  7. Configure Dark Mode in Galeria

    master

    Set the theme prop on the Galeria component to 'dark' to enable dark mode styling.

    import { Galeria } from '@nandorojo/galeria'
    
    export const DarkMode = () => (
      <Galeria urls={urls} theme="dark">
        ...
      </Galeria>
    )
  8. Use Galeria for a single image

    master

    To display a single image, wrap your image component with Galeria.Image inside a Galeria provider. Pass the image URL(s) as an array to the urls prop of Galeria.

    import { Galeria } from '@nandorojo/galeria'
    import { Image } from 'react-native'
    
    const url = 'https://my-image.com/image.jpg'
    
    export const SingleImage = ({ style }) => (
      <Galeria urls={[url]}>
        <Galeria.Image>
          <Image source={{ uri: url }} style={style} />
        </Galeria.Image>
      </Galeria>
    )
  9. Configure Metro for Galeria monorepo compatibility

    master

    When using Galeria in a monorepo or a project structure where react-native might be installed in a parent node_modules directory, you must configure metro.config.js to prevent version conflicts and ensure the bundler watches the correct folders.

    Specifically, you should:

    1. Exclude parent react-native: Add the parent node_modules/react-native to the resolver.blockList to prevent the bundler from using an incompatible version from the parent directory.
    2. Set nodeModulesPaths: Explicitly include both the local ./node_modules and the parent ../node_modules in the resolver paths.
    3. Configure watchFolders: Add the parent directory to watchFolders so Metro tracks changes in the workspace.
    4. Enable inlineRequires: Set inlineRequires: true in the transformer options for optimized performance.
    const { getDefaultConfig } = require('expo/metro-config')
    const path = require('path')
    
    const config = getDefaultConfig(__dirname)
    
    // Prevent incompatible react-native versions from parent folder
    config.resolver.blockList = [
      ...Array.from(config.resolver.blockList ?? []),
      new RegExp(path.resolve('..', 'node_modules', 'react-native')),
    ]
    
    // Ensure both local and parent node_modules are searched
    config.resolver.nodeModulesPaths = [
      path.resolve(__dirname, './node_modules'),
      path.resolve(__dirname, '../node_modules'),
    ]
    
    // Watch the parent directory for changes
    config.watchFolders = [path.resolve(__dirname, '..')]
    
    config.transformer.getTransformOptions = async () => ({
      transform: {
        experimentalImportSupport: false,
        inlineRequires: true,
      },
    })
    
    module.exports = config
  10. Configure Galeria for Expo Modules

    master

    The expo-module.config.json file defines the platform support and native module entry points for the Galeria library. This configuration allows the library to be integrated into Expo projects by specifying the native module classes for iOS and Android, and declaring support for web.

    {
      "platforms": ["ios", "android", "web"],
      "ios": {
        "modules": ["GaleriaModule"]
      },
      "android": {
        "modules": ["nandorojo.modules.galeria.GaleriaModule"]
      }
    }
  11. Customize Galeria.Image appearance (iOS only)

    master

    When using Galeria on iOS, you can use the following props on Galeria.Image to customize the viewer UI:

    • hideBlurOverlay: Hides the blur overlay that appears behind the image viewer.
    • hidePageIndicators: Hides the page indicator dots when viewing multiple images.
    // Hide Blur Overlay
    <Galeria.Image hideBlurOverlay>
      <Image source={{ uri: url }} style={style} />
    </Galeria.Image>
    
    // Hide Page Indicators
    <Galeria.Image hidePageIndicators>
      <Image source={{ uri: url }} style={style} />
    </Galeria.Image>
  12. Handle GaleriaView events

    master

    Galeria provides several event handlers to respond to user interactions:

    • onIndexChange: Fired when the currently viewed image changes. The event payload contains currentIndex.
    • onLongPress: Fired when the inline trigger image is long-pressed (before the viewer opens).
    • onPressRightNavItemIcon: Fired when the right navigation item icon is pressed. The event payload contains the index of the pressed item.
    • onDismiss: Fired when the gallery viewer is dismissed.
    interface GaleriaViewProps {
      onIndexChange?: (event: GaleriaIndexChangedEvent) => void;
      onLongPress?: (event: GaleriaLongPressEvent) => void;
      onPressRightNavItemIcon?: (event: GaleriaRightNavItemPressedEvent) => void;
      onDismiss?: (event: GaleriaDismissEvent) => void;
    }