npm package: @pol-studios/native-ui
Description: Native UI components for React Native (Expo)
Author: POL Studios
Latest version: 1.0.8

# @pol-studios/native-ui

Native iOS UI components for Expo apps built with SwiftUI. Features iOS 26 liquid glass effects with graceful fallbacks for older iOS versions.

## Requirements

- Expo SDK 55+
- iOS 17.0+ (some features require iOS 26+ for liquid glass effects)
- React Native 0.76+
- Requires Expo prebuild (not compatible with Expo Go)

## Installation

```bash
pnpm add @pol-studios/native-ui
npx expo prebuild --clean
npx expo run:ios
```

## Platform Support

| Platform | Support |
|----------|---------|
| **iOS** | Full native SwiftUI implementation |
| **Android** | JavaScript fallbacks (Reanimated/Gesture Handler) |
| **Web** | JavaScript fallbacks where applicable |

---

## Components

### PopoverMenu

Drag-to-select menu with glassmorphic highlights and haptic feedback.

```tsx
import { PopoverMenu, type PopoverMenuItemProp } from "@pol-studios/native-ui";

const items: PopoverMenuItemProp[] = [
  { key: "share", title: "Share", sfSymbol: "square.and.arrow.up", iconColor: "#007AFF" },
  { key: "edit", title: "Edit", sfSymbol: "pencil", iconColor: "#FF9500" },
  { key: "delete", title: "Delete", sfSymbol: "trash", iconColor: "#FF3B30", gradientColors: ["#FF3B30", "#FF6B6B"] },
];

<PopoverMenu
  items={items}
  onItemPress={(e) => console.log(e.nativeEvent.key)}
  side="top"        // top | right | bottom | left
  align="start"     // start | center | end
  sideOffset={8}
  iconSize={40}
  fontSize={19}
  rowHeight={70}
>
  <Pressable><Text>Open Menu</Text></Pressable>
</PopoverMenu>
```

**Features:**
- Drag finger across items - capsule highlight follows with spring animation
- Haptic feedback on each item change
- iOS 26 glass effect (falls back to ultraThinMaterial)
- 50pt dead zone buffer around edges

---

### SegmentedControl

iOS-style segmented picker with drag-to-switch and glass indicator.

```tsx
import { SegmentedControl, isSegmentedControlSupported } from "@pol-studios/native-ui";

const tabs = [
  { id: "all", title: "All", sfSymbol: "square.grid.2x2" },
  { id: "photos", title: "Photos", sfSymbol: "photo" },
  { id: "videos", title: "Videos", sfSymbol: "video", disabled: true },
];

<SegmentedControl
  tabs={tabs}
  selectedIndex={selectedIndex}
  onSelectedIndexChange={(e) => setSelectedIndex(e.nativeEvent.index)}
  height={44}
  indicatorStyle="glass"  // glass | solid | outline
  colors={{
    activeTextColor: "#FFFFFF",
    inactiveTextColor: "#8E8E93",
    backgroundColor: "rgba(120, 120, 128, 0.2)",
  }}
  fonts={{ fontWeight: "semibold", fontSize: 15 }}
  hapticsEnabled
  hapticStyle="selection"
/>
```

---

### DynamicIslandToast

Notifications that animate from the Dynamic Island (or notch area).

```tsx
import { ToastProvider, useToast } from "@pol-studios/native-ui";

// Wrap your app
<ToastProvider>
  <App />
</ToastProvider>

// Use the hook
function MyComponent() {
  const toast = useToast();

  toast.success("Saved!", { message: "Your changes were saved" });
  toast.error("Failed", { message: "Please try again" });
  toast.info("Update Available");
  toast.warning("Low Storage");

  // Custom toast
  toast.show({
    symbol: "sparkles",
    symbolPrimaryColor: "#FFD700",
    symbolSecondaryColor: "#FF6B00",
    title: "Achievement Unlocked!",
    duration: 4000,
    hapticFeedback: "success",
  });
}
```

---

### ExpandableGlassContainer

Floating action button that morphs into an action grid with liquid glass.

```tsx
import { ExpandableGlassContainer, GlassAction } from "@pol-studios/native-ui";

<ExpandableGlassContainer
  position="bottom-right"
  size="medium"
  onActionPress={(id) => console.log(id)}
>
  <GlassAction id="camera" sfSymbol="camera.fill" label="Camera" iconColor="#007AFF" />
  <GlassAction id="photo" sfSymbol="photo.fill" label="Photo" iconColor="#34C759" />
  <GlassAction id="document" sfSymbol="doc.fill" label="Document" iconColor="#FF9500" />
</ExpandableGlassContainer>
```

---

### MorphingTabBar

Animated tab bar with quick actions grid and integrated search.

```tsx
import { MorphingTabBar } from "@pol-studios/native-ui";

const tabs = [
  { key: "home", label: "Home", sfSymbol: "house", sfSymbolSelected: "house.fill" },
  { key: "search", label: "Search", sfSymbol: "magnifyingglass" },
  { key: "library", label: "Library", sfSymbol: "books.vertical", badgeValue: "3" },
];

const quickActions = [
  { key: "camera", label: "Camera", sfSymbol: "camera.fill", iconColor: "#007AFF" },
  { key: "note", label: "Note", sfSymbol: "note.text", iconColor: "#FF9500" },
];

<MorphingTabBar
  tabs={tabs}
  quickActions={quickActions}
  selectedTabKey={selectedTab}
  expanded={expanded}
  showSearch
  searchText={searchText}
  onTabChange={(e) => setSelectedTab(e.nativeEvent.key)}
  onQuickActionPress={(e) => console.log(e.nativeEvent.key)}
  onExpandChange={(e) => setExpanded(e.nativeEvent.expanded)}
  onSearchChange={(e) => setSearchText(e.nativeEvent.text)}
/>
```

---

### TruncationText

Text with native truncation and "see more" tap target.

```tsx
import { TruncationText } from "@pol-studios/native-ui";

<TruncationText
  text={longText}
  numberOfLines={3}
  fontSize={16}
  textColor="#FFFFFF"
  truncationText="...see more"
  truncationColor="#007AFF"
  onTruncationPress={() => setExpanded(true)}
/>
```

---

### AnimatedKeypad

Numeric keypad with spring animations and biometric support.

```tsx
import { AnimatedKeypad } from "@pol-studios/native-ui";

<AnimatedKeypad
  onKeyPress={(key) => console.log(key)}
  onDelete={() => console.log("delete")}
  showBiometric
  biometricType="faceId"
  onBiometricPress={() => authenticate()}
/>
```

---

### MediaCardStack

Tinder-style swipeable card stack for media.

```tsx
import { MediaCardStack } from "@pol-studios/native-ui";

<MediaCardStack
  cards={[
    { id: "1", imageUrl: "https://...", title: "Card 1" },
    { id: "2", imageUrl: "https://...", title: "Card 2" },
  ]}
  onSwipe={(e) => console.log(e.nativeEvent.direction, e.nativeEvent.cardId)}
  onCardPress={(e) => console.log(e.nativeEvent.cardId)}
/>
```

---

### Onboarding

Native onboarding flow with page indicators.

```tsx
import { Onboarding } from "@pol-studios/native-ui";

<Onboarding
  pages={[
    { title: "Welcome", subtitle: "Get started", sfSymbol: "hand.wave.fill", symbolColor: "#007AFF" },
    { title: "Features", subtitle: "Explore", sfSymbol: "sparkles", symbolColor: "#FF9500" },
  ]}
  onComplete={() => navigation.navigate("Home")}
  onPageChange={(e) => console.log(e.nativeEvent.index)}
  primaryButtonTitle="Continue"
  secondaryButtonTitle="Skip"
/>
```

---

### Toolbar

Native bottom toolbar with glass effects.

```tsx
import { Toolbar, isToolbarSupported } from "@pol-studios/native-ui";

<Toolbar
  items={toolbarItems}
  onButtonPress={handlePress}
  selectedKey={selectedKey}
  colors={{
    glassBackground: 'rgba(255,255,255,0.1)',
    neutral300: '#d4d4d4',
    sapphire500: '#3b82f6',
  }}
/>
```

---

## All Exports

```tsx
// Main entry point
import {
  // PopoverMenu
  PopoverMenu,

  // SegmentedControl
  SegmentedControl,
  isSegmentedControlSupported,

  // DynamicIslandToast
  DynamicIslandToast,
  ToastProvider,
  useToast,

  // GlassMorphing
  ExpandableGlassContainer,
  GlassAction,
  GlassMorphingView,

  // MorphingTabBar
  MorphingTabBar,
  MorphingTabsLayout,

  // Toolbar
  Toolbar,
  isToolbarSupported,

  // TruncationText
  TruncationText,

  // AnimatedKeypad
  AnimatedKeypad,

  // MediaCardStack
  MediaCardStack,

  // Onboarding
  Onboarding,

  // WidgetBridge
  WidgetBridge,

  // ContextMenu
  ContextMenu,

  // Menu
  Menu,
} from "@pol-studios/native-ui";
```

---

## iOS Version Support

| iOS Version | Glass Effects | Fallback |
|-------------|---------------|----------|
| iOS 26+ | Full liquid glass with `glassEffect()` | - |
| iOS 17-25 | `.ultraThinMaterial` | Blurred material |

All components automatically detect iOS version and apply appropriate effects.

---

## Peer Dependencies

| Peer Dependency | Required For |
|-----------------|--------------|
| `expo` | All modules (required) |
| `expo-modules-core` | All modules (required) |
| `react` | All modules (required) |
| `react-native` | All modules (required) |
| `react-native-reanimated` | MediaCardStack (Android fallback) |
| `react-native-gesture-handler` | MediaCardStack (Android fallback) |
| `react-native-toast-message` | DynamicIslandToast (Android/Web fallback) |
| `expo-image` | MediaCardStack, Onboarding |
| `expo-router` | MorphingTabsLayout |
| `expo-haptics` | AnimatedKeypad, Toolbar |
| `react-native-safe-area-context` | MorphingTabBar |

---

## Architecture

This package uses the Expo Modules API to bridge SwiftUI components to React Native:

```
/packages/@pol/native-ui/
├── src/                          # TypeScript sources
│   ├── popover-menu/
│   ├── segmented-control/
│   ├── dynamic-island-toast/
│   └── ...
├── ios/                          # Swift implementations
│   ├── PopoverMenu/
│   ├── SegmentedControl/
│   ├── Shared/
│   │   └── GlassEffectCompat.swift  # iOS 26+ glass helpers
│   └── ...
├── examples/
│   └── NativeUIShowcase.tsx      # Complete working demo
└── expo-module.config.json
```

### Platform Files

Metro resolves platform-specific implementations:
- `Component.ios.tsx` - Native iOS via Expo Module
- `Component.android.tsx` - Reanimated/JS fallback
- `Component.tsx` - Default (web)

---

## Development

```bash
# Build the package
pnpm build

# Watch mode
pnpm dev

# Type check
pnpm typecheck
```

---

## Example

See `/examples/NativeUIShowcase.tsx` for a complete working demo:

```tsx
import NativeUIShowcase from "@pol-studios/native-ui/examples/NativeUIShowcase";

export default function App() {
  return <NativeUIShowcase />;
}
```

---

## License

UNLICENSED - Private package