@pol-studios/native-ui
v1.0.8
Published
Native UI components for React Native (Expo)
Downloads
846
Maintainers
Readme
@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
pnpm add @pol-studios/native-ui
npx expo prebuild --clean
npx expo run:iosPlatform 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.
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.
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).
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.
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.
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.
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.
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.
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.
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.
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
// 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.jsonPlatform Files
Metro resolves platform-specific implementations:
Component.ios.tsx- Native iOS via Expo ModuleComponent.android.tsx- Reanimated/JS fallbackComponent.tsx- Default (web)
Development
# Build the package
pnpm build
# Watch mode
pnpm dev
# Type check
pnpm typecheckExample
See /examples/NativeUIShowcase.tsx for a complete working demo:
import NativeUIShowcase from "@pol-studios/native-ui/examples/NativeUIShowcase";
export default function App() {
return <NativeUIShowcase />;
}License
UNLICENSED - Private package
