HeroUI Native Example

repository·main·Indexed 41 days ago

https://github.com/heroui-inc/heroui-native-example

A React Native demonstration project showcasing HeroUI Native, a modern UI library for building customizable mobile interfaces. Features include accessible components, TypeScript support, and styling via Uniwind/Tailwind CSS. The example app utilizes Expo Router for file-based routing and includes implementations for multi-step BottomSheets, Raycast-style ModelSelect, and a vertical showcase Carousel.

Tokens
8.2K
Snippets
29
Records
32
Agent score
52%

What's inside HeroUI Native

  1. Reset the project to a clean state

    main

    If you want to start a new project from a clean slate, run the reset-project script. This command performs the following actions:

    1. Moves the existing src directory to app-example-src.
    2. Creates a new src/app directory containing a basic HeroUI Native setup ready for development.
    npm run reset-project
  2. Install and run the HeroUI Native Example App

    main

    To set up the example project locally, clone the repository, install the dependencies using npm, and start the development server using Expo.

    Note: This project uses file-based routing via Expo Router. You can begin developing by modifying files within the src/app directory.

    git clone https://github.com/heroui-inc/heroui-native-example.git
    cd heroui-native-example
    npm install
    npx expo start
  3. Configure Metro with Uniwind

    main

    The project uses withUniwindConfig to wrap the default Expo Metro configuration. This enables Uniwind's CSS and type generation capabilities within the Metro bundler. You must provide the base Expo configuration obtained via getDefaultConfig(__dirname) to the wrapper.

    const { getDefaultConfig } = require("expo/metro-config");
    const { withUniwindConfig } = require('uniwind/metro');
    
    const config = getDefaultConfig(__dirname);
    
    module.exports = withUniwindConfig(config, {
      // Uniwind options go here
    });
  4. Configure ESLint for HeroUI Native Example

    main

    The project uses ESLint with the eslint-config-expo/flat configuration to ensure compatibility with the Expo environment. The configuration is defined using defineConfig from eslint/config and includes specific ignore patterns to prevent linting of generated or auxiliary directories.

    const { defineConfig } = require('eslint/config');
    const expoConfig = require('eslint-config-expo/flat');
    
    module.exports = defineConfig([
      expoConfig,
      {
        ignores: [
          'dist/*',
          '.expo/**',
          '.sync/**',
          '.upstream/**',
        ],
      },
    ]);
  5. Configure Lingui catalog settings

    main

    The Lingui configuration defines how localization catalogs are generated, which locales are supported, and which files are included or excluded from translation.

    Key configuration properties:

    • sourceLocale: The primary language of the codebase (e.g., en).
    • locales: An array of supported locale identifiers (e.g., ['en', 'ar', 'he']).
    • fallbackLocales: Defines the default locale to use when a translation is missing. Use the default key to specify the fallback (e.g., { default: 'en' }).
    • format: Specifies the output format for message files. In this project, it uses @lingui/format-po with lineNumbers: false.
    • catalogs: An array of catalog objects defining where messages are stored and which source files to scan.

    Catalog Object Properties:

    • path: The template path for message files, using {locale} as a placeholder (e.g., <rootDir>/src/locales/{locale}/messages).
    • include: Glob patterns for source files to be scanned for translatable strings.
    • exclude: Glob patterns for files or directories to ignore during scanning. Note that in this project, showcase components are intentionally excluded to focus on component RTL previewing rather than marketing copy translation.
    import { defineConfig } from "@lingui/cli";
    import { formatter } from "@lingui/format-po";
    
    export default defineConfig({
      sourceLocale: "en",
      locales: ["en", "ar", "he"],
      fallbackLocales: {
        default: "en",
      },
      format: formatter({ lineNumbers: false }),
      catalogs: [
        {
          path: "<rootDir>/src/locales/{locale}/messages",
          include: ["<rootDir>/src"],
          exclude: [
            "<rootDir>/src/components/showcases",
            "<rootDir>/src/app/(home)/showcases",
            "<rootDir>/src/locales",
          ],
        },
      ],
    });
  6. Configure supported locales and RTL settings

    main

    The application uses a centralized localization system where layout direction (LTR vs RTL) is derived directly from the selected locale.

    • APP_LOCALES: The complete list of supported locale codes (e.g., 'en', 'ar', 'he').
    • RTL_LOCALES: A subset of APP_LOCALES that triggers right-to-left layout direction.
    • APP_LOCALE_LABELS: A mapping of locale codes to their endonym display names (e.g., 'ar': 'العربية').
    • DEFAULT_LOCALE: The locale used on the first launch (defaults to 'en').

    When adding a new language, ensure that if it is a right-to-left language, it is added to both APP_LOCALES and RTL_LOCALES to ensure correct direction resolution.

    export const RTL_LOCALES = ['ar', 'he'] as const;
    export const APP_LOCALES = ['en', 'ar', 'he'] as const;
    export const DEFAULT_LOCALE: AppLocale = 'en';
    
    export const APP_LOCALE_LABELS: Record<AppLocale, string> = {
      en: 'English',
      ar: 'العربية',
      he: 'עברית',
    };
  7. Implement a detached multi-step BottomSheet

    main

    You can create a multi-step flow within a BottomSheet by using the detached prop on BottomSheet.Content. To achieve smooth transitions between steps (e.g., changing content, headers, or footers), wrap your layout sections in Animated.View from react-native-reanimated and provide a layout prop (such as LinearTransition.springify()).

    Key components used in this pattern:

    • BottomSheet.Trigger: Used to open the sheet (often via asChild).
    • BottomSheet.Portal: Required to render the sheet content.
    • BottomSheet.Overlay: The background dimming layer.
    • BottomSheet.Content: The main container. Use the detached prop to allow the sheet to float above the bottom inset.
    • BottomSheet.Title and BottomSheet.Description: Standard text components for the sheet content.
    • BottomSheet.Close: A built-in component to handle closing the sheet.

    To manage steps, maintain a local state for the current step and conditionally render different UI elements (Header, Content, Footer) based on that state. Using entering and exiting animations (like FadeIn and FadeOut) on the step-specific views provides a polished feel during transitions.

    <BottomSheet isOpen={isOpen} onOpenChange={handleOpenChange}>
      <BottomSheet.Trigger asChild>
        <Button>Open Multi-step</Button>
      </BottomSheet.Trigger>
    
      <BottomSheet.Portal>
        <BottomSheet.Overlay />
        <BottomSheet.Content
          detached
          bottomInset={12}
          className="mx-4"
          backgroundClassName="rounded-4xl"
        >
          {/* Header Section */}
          <Animated.View layout={LinearTransition.springify()}>
            <BottomSheet.Title>{step === 1 ? 'Step 1' : 'Step 2'}</BottomSheet.Title>
            <BottomSheet.Close />
          </Animated.View>
    
          {/* Content Section */}
          <Animated.View layout={LinearTransition.springify()}>
            {step === 1 && (
              <Animated.View entering={FadeIn} exiting={FadeOut}>
                <BottomSheet.Description>Content for step 1</BottomSheet.Description>
              </Animated.View>
            )}
            {step === 2 && (
              <Animated.View entering={FadeIn} exiting={FadeOut}>
                <BottomSheet.Description>Content for step 2</BottomSheet.Description>
              </Animated.View>
            )}
          </Animated.View>
    
          {/* Footer Section */}
          <Animated.View layout={LinearTransition.springify()}>
            <Button onPress={() => setStep(2)}>Next</Button>
          </Animated.View>
        </BottomSheet.Content>
      </BottomSheet.Portal>
    </BottomSheet>
  8. Use UsageVariantsSelect for variant selection

    main

    The UsageVariantsSelect component is a specialized implementation of the Select component designed to handle UsageVariant data types. It manages the selection of different usage variants (like themes or modes) and automatically handles localization using Lingui descriptors.

    When a value is selected, it triggers the setVariant callback and attempts to scroll the provided listRef to the selected item's index.

    Props

    • data: An array of UsageVariant objects.
    • variant: The currently active UsageVariant.
    • setVariant: A function to update the active variant.
    • listRef: A React.RefObject pointing to a FlatList of UsageVariant items, used for auto-scrolling after selection.
    import { UsageVariantsSelect } from './components/component-presentation/usage-variants-select';
    
    // Usage within a parent component:
    <UsageVariantsSelect
      data={variantData}
      variant={currentVariant}
      setVariant={setCurrentVariant}
      listRef={flatListRef}
    />
  9. useProgressState API Reference

    main

    The useProgressState hook manages a global progress value keyed by progress-toast-state.

    Parameters:

    • initialProgress (number, default: 0): The starting progress value (0-100).

    Returns: An object containing:

    • progress (number): The current progress value (clamped between 0 and 100).
    • setProgress (function): A function to update the progress. It automatically clamps input values between 0 and 100.
    • resetProgress (function): A function to reset the progress state to its initial value (0).