From 8d86320dcdb3897230e189b682c1d5f6a3fa0651 Mon Sep 17 00:00:00 2001 From: Ma'sum Date: Sat, 19 Sep 2026 09:09:15 +0700 Subject: [PATCH] refactor: remove BottomSheet, Sheet, and Tabs components and add LLM documentation --- apps/docs/public/llm.txt | 69 +++ apps/docs/public/llms.txt | 69 +++ apps/docs/public/rn-ui/llms.txt | 527 ++++++++++++++++++ apps/docs/src/content/docs/index.mdx | 2 +- .../docs/src/content/docs/rn-ui/changelog.mdx | 24 +- .../docs/rn-ui/components/bottom-sheet.mdx | 46 -- .../content/docs/rn-ui/components/index.mdx | 7 +- .../content/docs/rn-ui/components/sheet.mdx | 102 ---- .../content/docs/rn-ui/components/tabs.mdx | 45 -- apps/docs/src/content/docs/rn-ui/index.mdx | 30 +- .../src/content/docs/rn-ui/installation.mdx | 10 +- apps/native/app/_layout.tsx | 5 +- apps/native/app/rn-ui/[id].tsx | 9 - apps/native/app/rn-ui/index.tsx | 20 +- apps/native/components/rn-ui/index.ts | 3 - .../rn-ui/sections/BottomSheetSection.tsx | 66 --- .../rn-ui/sections/SheetSection.tsx | 95 ---- .../components/rn-ui/sections/TabsSection.tsx | 113 ---- .../components/rn-ui/useSectionContext.tsx | 11 +- apps/native/package.json | 1 - bun.lock | 21 +- package.json | 1 - packages/rn-ui/CHANGELOG.md | 12 + packages/rn-ui/README.md | 8 +- packages/rn-ui/package.json | 3 +- packages/rn-ui/src/components/BottomSheet.tsx | 263 --------- packages/rn-ui/src/components/Command.tsx | 279 ++++++---- packages/rn-ui/src/components/Sheet.tsx | 346 ------------ packages/rn-ui/src/components/Tabs.tsx | 370 ------------ packages/rn-ui/src/components/index.ts | 42 -- 30 files changed, 919 insertions(+), 1680 deletions(-) create mode 100644 apps/docs/public/llm.txt create mode 100644 apps/docs/public/llms.txt create mode 100644 apps/docs/public/rn-ui/llms.txt delete mode 100644 apps/docs/src/content/docs/rn-ui/components/bottom-sheet.mdx delete mode 100644 apps/docs/src/content/docs/rn-ui/components/sheet.mdx delete mode 100644 apps/docs/src/content/docs/rn-ui/components/tabs.mdx delete mode 100644 apps/native/components/rn-ui/sections/BottomSheetSection.tsx delete mode 100644 apps/native/components/rn-ui/sections/SheetSection.tsx delete mode 100644 apps/native/components/rn-ui/sections/TabsSection.tsx delete mode 100644 packages/rn-ui/src/components/BottomSheet.tsx delete mode 100644 packages/rn-ui/src/components/Sheet.tsx delete mode 100644 packages/rn-ui/src/components/Tabs.tsx diff --git a/apps/docs/public/llm.txt b/apps/docs/public/llm.txt new file mode 100644 index 0000000..ed5c17d --- /dev/null +++ b/apps/docs/public/llm.txt @@ -0,0 +1,69 @@ +# Masum Dev — LLM Knowledge Base & Monorepo Index + +> High-performance, open-source React Native & Expo component libraries and tools. +> Website: https://react-native-library-docs.vercel.app + +This repository provides open-source libraries under the `@masumdev/*` namespace, built using a Turborepo + Bun monorepo architecture for mobile (iOS / Android / Web) and developer tooling. + +--- + +## 🎯 Primary Focus: @masumdev/rn-ui (React Native UI Kit) + +`@masumdev/rn-ui` is a production-grade, typed, flat-aesthetic React Native UI library with 61 components, a dynamic theme engine, zero-elevation defaults, and zero-bounce micro-animations. + +👉 **[Read the Full React Native Context & Native Recipes Guide](/rn-ui/llms.txt)** +- File path: `/rn-ui/llms.txt` +- Contains complete production recipes: Root setup (`_layout.tsx`), Theme tokens (`useTheme`), Form validation architecture, High-performance loading (`Spinner`, `SkeletonGroup`), Dialogs (`AlertDialog`), Cupertino segment switcher (`SegmentedControl`), Lucide icon adapter (`renderIcon`), and anti-patterns. + +### Quick Start +```bash +# Expo +npx expo install react-native-reanimated react-native-worklets react-native-gesture-handler react-native-safe-area-context +bun add @masumdev/rn-ui + +# React Native CLI +bun add @masumdev/rn-ui react-native-reanimated react-native-worklets react-native-gesture-handler react-native-safe-area-context +``` + +### Essential Rules for AI Agents: +- **Variant**: The filled button/badge variant is strictly `"filled"` (NEVER use `"solid"`). +- **Tone**: The neutral tone is strictly `"default"` (NEVER use `"neutral"`). +- **Icons**: Always wrap icon objects with `renderIcon` or render props — NEVER pass raw forwardRef icon objects as JSX children. +- **Animations**: Zero-bounce policy — always use linear or cubic easing (never bouncy spring overshoot). +- **Removed in v0.4.0**: `@gorhom/bottom-sheet`, `BottomSheet`, `Sheet`, and `Tabs` have been purged. Use native ``, ``, or ``. + +--- + +## 📦 Other Open-Source Libraries in Monorepo + +### 1. @masumdev/rn-qr-code +High-performance SVG QR code generator for React Native and Expo with custom eye shapes and presets. +- **Install**: `bun add @masumdev/rn-qr-code react-native-svg` +- **Key Features**: Supports custom corner eye shapes (`square`, `circle`, `rounded`, `leaf`), embedded central logos, gradient colors, and instant export/sharing. +- **Documentation**: https://react-native-library-docs.vercel.app/rn-qr-code/ + +### 2. @masumdev/rn-tajweed-verse +Tajweed Quranic verse parser and renderer with interactive color-coded pronunciation rules and tooltips. +- **Install**: `bun add @masumdev/rn-tajweed-verse react-native-svg` +- **Key Features**: Color-coded Tajweed rules (Ghunnah, Ikhfa, Idgham, Qalqalah, Madd), interactive tapping for rule explanation tooltips, customizable fonts and color schemes. +- **Documentation**: https://react-native-library-docs.vercel.app/rn-tajweed-verse/ + +### 3. @masumdev/tscheck +High-speed TypeScript and AST checking CLI tool for monorepos and libraries. +- **Key Features**: Deprecation scanner, unused variable/import detector, explicit `any` finder, and circular dependency cycle analyzer. +- **Documentation**: https://react-native-library-docs.vercel.app/tscheck/ + +### 4. @masumdev/markforge +Markdown and documentation generator with terminal ink UI and AST parsing. +- **Documentation**: https://react-native-library-docs.vercel.app/markforge/ + +--- + +## 🔗 Documentation Links + +- **RN UI Overview**: https://react-native-library-docs.vercel.app/rn-ui/ +- **RN UI 61+ Component Catalog**: https://react-native-library-docs.vercel.app/rn-ui/components/ +- **RN UI Theming Guide**: https://react-native-library-docs.vercel.app/rn-ui/theming/ +- **RN UI Installation**: https://react-native-library-docs.vercel.app/rn-ui/installation/ +- **RN UI Changelog**: https://react-native-library-docs.vercel.app/rn-ui/changelog/ +- **GitHub Repository**: https://github.com/masumrpg/react-native-library diff --git a/apps/docs/public/llms.txt b/apps/docs/public/llms.txt new file mode 100644 index 0000000..ed5c17d --- /dev/null +++ b/apps/docs/public/llms.txt @@ -0,0 +1,69 @@ +# Masum Dev — LLM Knowledge Base & Monorepo Index + +> High-performance, open-source React Native & Expo component libraries and tools. +> Website: https://react-native-library-docs.vercel.app + +This repository provides open-source libraries under the `@masumdev/*` namespace, built using a Turborepo + Bun monorepo architecture for mobile (iOS / Android / Web) and developer tooling. + +--- + +## 🎯 Primary Focus: @masumdev/rn-ui (React Native UI Kit) + +`@masumdev/rn-ui` is a production-grade, typed, flat-aesthetic React Native UI library with 61 components, a dynamic theme engine, zero-elevation defaults, and zero-bounce micro-animations. + +👉 **[Read the Full React Native Context & Native Recipes Guide](/rn-ui/llms.txt)** +- File path: `/rn-ui/llms.txt` +- Contains complete production recipes: Root setup (`_layout.tsx`), Theme tokens (`useTheme`), Form validation architecture, High-performance loading (`Spinner`, `SkeletonGroup`), Dialogs (`AlertDialog`), Cupertino segment switcher (`SegmentedControl`), Lucide icon adapter (`renderIcon`), and anti-patterns. + +### Quick Start +```bash +# Expo +npx expo install react-native-reanimated react-native-worklets react-native-gesture-handler react-native-safe-area-context +bun add @masumdev/rn-ui + +# React Native CLI +bun add @masumdev/rn-ui react-native-reanimated react-native-worklets react-native-gesture-handler react-native-safe-area-context +``` + +### Essential Rules for AI Agents: +- **Variant**: The filled button/badge variant is strictly `"filled"` (NEVER use `"solid"`). +- **Tone**: The neutral tone is strictly `"default"` (NEVER use `"neutral"`). +- **Icons**: Always wrap icon objects with `renderIcon` or render props — NEVER pass raw forwardRef icon objects as JSX children. +- **Animations**: Zero-bounce policy — always use linear or cubic easing (never bouncy spring overshoot). +- **Removed in v0.4.0**: `@gorhom/bottom-sheet`, `BottomSheet`, `Sheet`, and `Tabs` have been purged. Use native ``, ``, or ``. + +--- + +## 📦 Other Open-Source Libraries in Monorepo + +### 1. @masumdev/rn-qr-code +High-performance SVG QR code generator for React Native and Expo with custom eye shapes and presets. +- **Install**: `bun add @masumdev/rn-qr-code react-native-svg` +- **Key Features**: Supports custom corner eye shapes (`square`, `circle`, `rounded`, `leaf`), embedded central logos, gradient colors, and instant export/sharing. +- **Documentation**: https://react-native-library-docs.vercel.app/rn-qr-code/ + +### 2. @masumdev/rn-tajweed-verse +Tajweed Quranic verse parser and renderer with interactive color-coded pronunciation rules and tooltips. +- **Install**: `bun add @masumdev/rn-tajweed-verse react-native-svg` +- **Key Features**: Color-coded Tajweed rules (Ghunnah, Ikhfa, Idgham, Qalqalah, Madd), interactive tapping for rule explanation tooltips, customizable fonts and color schemes. +- **Documentation**: https://react-native-library-docs.vercel.app/rn-tajweed-verse/ + +### 3. @masumdev/tscheck +High-speed TypeScript and AST checking CLI tool for monorepos and libraries. +- **Key Features**: Deprecation scanner, unused variable/import detector, explicit `any` finder, and circular dependency cycle analyzer. +- **Documentation**: https://react-native-library-docs.vercel.app/tscheck/ + +### 4. @masumdev/markforge +Markdown and documentation generator with terminal ink UI and AST parsing. +- **Documentation**: https://react-native-library-docs.vercel.app/markforge/ + +--- + +## 🔗 Documentation Links + +- **RN UI Overview**: https://react-native-library-docs.vercel.app/rn-ui/ +- **RN UI 61+ Component Catalog**: https://react-native-library-docs.vercel.app/rn-ui/components/ +- **RN UI Theming Guide**: https://react-native-library-docs.vercel.app/rn-ui/theming/ +- **RN UI Installation**: https://react-native-library-docs.vercel.app/rn-ui/installation/ +- **RN UI Changelog**: https://react-native-library-docs.vercel.app/rn-ui/changelog/ +- **GitHub Repository**: https://github.com/masumrpg/react-native-library diff --git a/apps/docs/public/rn-ui/llms.txt b/apps/docs/public/rn-ui/llms.txt new file mode 100644 index 0000000..2540957 --- /dev/null +++ b/apps/docs/public/rn-ui/llms.txt @@ -0,0 +1,527 @@ +# @masumdev/rn-ui — AI Developer & Context Guide + +> Production-ready, typed, flat-aesthetic React Native & Expo component library. +> Version: 0.4.0 | 61 Components | Monorepo: Masum Dev + +This document is optimized for LLMs and AI coding assistants writing React Native / Expo applications using `@masumdev/rn-ui`. + +--- + +## 🏛️ Core Design Philosophy & Architectural Rules + +1. **Flat Aesthetic (Zero Elevation Default)**: + - Components use clean borders, subtle tonal tinting, and translucent surfaces instead of drop shadows or heavy skeuomorphic elevations. + - Avoid adding random shadow styles unless explicitly instructed. +2. **Strict Orthogonal `tone` vs `variant`**: + - **`tone` (Semantic Intent)**: `"default"` | `"primary"` | `"secondary"` | `"accent"` | `"success"` | `"warning"` | `"danger"` | `"info"`. + - **`variant` (Visual Treatment)**: `"filled"` | `"outline"` | `"ghost"` | `"soft"`. + - ⚠️ **CRITICAL GOTCHA**: + - Do **NOT** use `variant="solid"` (use `"filled"`). + - Do **NOT** use `tone="neutral"` (use `"default"`). +3. **Zero-Bounce Animation Policy**: + - All Reanimated transitions MUST use linear or cubic easing (e.g., `Easing.bezier(0.25, 0.1, 0.25, 1)` or `withTiming` duration 150–250ms). + - Spring overshoots and bounces are strictly disallowed. +4. **Zero Dependency on `@gorhom/bottom-sheet`**: + - In `v0.4.0+`, `BottomSheet`, `Sheet`, and `Tabs` have been completely removed. + - For bottom sheets / modals, use React Native's native `` or ``. + - For tab switching, use ``. +5. **Pluggable Iconography (`renderIcon`)**: + - Icons are not hardcoded to any icon library. + - When passing icons (e.g. from `lucide-react-native`), use the `renderIcon` helper or standard render prop: `({ color, size }) => `. + - ⚠️ Never pass a Lucide icon component directly as raw JSX children (e.g. ``), as it causes React child object errors. + +--- + +## 📦 Installation & Setup + +### Required Peer Dependencies +```bash +# Expo +npx expo install react-native-reanimated react-native-worklets react-native-gesture-handler react-native-safe-area-context +bun add @masumdev/rn-ui + +# React Native CLI +bun add @masumdev/rn-ui react-native-reanimated react-native-worklets react-native-gesture-handler react-native-safe-area-context +``` + +### App Root Configuration (`app/_layout.tsx` / `App.tsx`) +Always wrap the application root with `GestureHandlerRootView`, `ThemeProvider`, and `ToastProvider`: + +```tsx +import React from 'react'; +import { Stack } from 'expo-router'; +import { GestureHandlerRootView } from 'react-native-gesture-handler'; +import * as SecureStore from 'expo-secure-store'; +import { + ThemeProvider, + ToastProvider, + defaultThemes, + type ThemeStorage, +} from '@masumdev/rn-ui'; + +// Optional: Persistent theme storage via SecureStore or AsyncStorage +const themeStorage: ThemeStorage = { + getItem: (key: string) => SecureStore.getItemAsync(key), + setItem: (key: string, value: string) => SecureStore.setItemAsync(key, value), +}; + +export default function RootLayout() { + return ( + + + + + + + + ); +} +``` + +--- + +## 🎨 Theme Engine & Design Tokens + +Access theme colors, spacing, typography, and color schemes via `useTheme()`: + +```tsx +import React from 'react'; +import { View, StyleSheet } from 'react-native'; +import { useTheme, useThemeStyles, Text } from '@masumdev/rn-ui'; + +export function ThemedCard() { + const { colors, radii, spacing, typography, isDark, setColorScheme } = useTheme(); + + // Or use memoized theme styles factory: + const styles = useThemeStyles((theme) => ({ + card: { + backgroundColor: theme.colors.surface, + borderColor: theme.colors.border, + borderWidth: 1, + borderRadius: theme.radii.xl, + padding: theme.spacing.lg, + gap: theme.spacing.sm, + }, + })); + + return ( + + Themed Surface + Current mode: {isDark ? 'Dark' : 'Light'} + + ); +} +``` + +### Token Palette Reference +- **Colors**: `primary`, `primarySoft`, `secondary`, `accent`, `success`, `warning`, `danger`, `info`, `background`, `backgroundMuted`, `surface`, `surfaceMuted`, `surfaceRaised`, `border`, `borderMuted`, `text`, `textMuted`, `textSubtle`, `textInverse`. +- **Spacing**: `none` (0), `xxs` (2), `xs` (4), `sm` (8), `md` (12), `lg` (16), `xl` (24), `xxl` (32), `xxxl` (40). +- **Radii**: `none` (0), `xs` (4), `sm` (6), `md` (8), `lg` (12), `xl` (16), `xxl` (24), `full` (9999). +- **Typography Variants**: `display`, `h1`, `h2`, `h3`, `title`, `subtitle`, `body`, `bodySmall`, `label`, `labelSmall`, `caption`. + +--- + +## 🛠️ Real Native Production Recipes + +### 1. Interactive Buttons with Lucide Icons +```tsx +import React from 'react'; +import { View } from 'react-native'; +import { Button, IconButton, ButtonGroup } from '@masumdev/rn-ui'; +import { ArrowRight, Trash2, Send } from 'lucide-react-native'; + +export function ButtonExamples() { + const [loading, setLoading] = React.useState(false); + + return ( + + {/* Standard Filled Button with Right Icon */} + + + {/* Danger Outline with Left Icon */} + + + {/* Standalone Icon Button */} + } + tone="primary" + variant="soft" + size="md" + accessibilityLabel="Send message" + onPress={() => console.log('Sent')} + /> + + ); +} +``` + +### 2. Comprehensive Form Validation Recipe +```tsx +import React, { useState } from 'react'; +import { View } from 'react-native'; +import { + FormField, + Label, + Input, + InputGroup, + Select, + Checkbox, + Switch, + Button, +} from '@masumdev/rn-ui'; +import { Mail } from 'lucide-react-native'; + +export function LoginForm() { + const [email, setEmail] = useState(''); + const [role, setRole] = useState('developer'); + const [remember, setRemember] = useState(true); + const [notifications, setNotifications] = useState(false); + const [emailError, setEmailError] = useState(); + + const handleSubmit = () => { + if (!email.includes('@')) { + setEmailError('Please enter a valid email address'); + return; + } + setEmailError(undefined); + console.log({ email, role, remember, notifications }); + }; + + return ( + + {/* Input inside FormField with error helper */} + + } + > + { + setEmail(text); + if (emailError) setEmailError(undefined); + }} + placeholder="you@example.com" + keyboardType="email-address" + autoCapitalize="none" + /> + + + + {/* Dropdown Select */} + + - - {/* Command Items List */} - - {filtered.length === 0 ? ( - - - {emptyText} + {/* Header */} + + + {title} + + { + triggerHaptic("selection"); + onClose(); + }} + hitSlop={8} + style={({ pressed }) => ({ + padding: spacing.xs, + borderRadius: radii.full, + opacity: pressed ? 0.6 : 1, + })} + accessibilityLabel="Close" + > + + ✕ - - ) : ( - Object.entries(groupedItems).map(([groupName, groupList]) => ( - - - {groupName} - - - {groupList.map((item) => ( - handleSelect(item)} - style={({ pressed }) => ({ - minHeight: 48, - borderRadius: radii.xl, - paddingVertical: spacing.md, - paddingHorizontal: spacing.md, - flexDirection: "row", - alignItems: "center", - gap: spacing.md, - backgroundColor: pressed - ? colors.backgroundMuted - : colors.surfaceMuted, - borderWidth: 1, - borderColor: pressed ? colors.border : colors.borderMuted, - opacity: item.disabled ? 0.5 : 1, - })} - > - {item.icon ? renderIcon(item.icon, colors.primary, 18) : null} + + - - - {item.label} - - {item.description ? ( - - {item.description} - - ) : null} - + + {/* Search Input */} + - {item.badge ? ( - - {item.badge} - - ) : null} + {/* Command Items List */} + + {filtered.length === 0 ? ( + + + {emptyText} + + + ) : ( + Object.entries(groupedItems).map(([groupName, groupList]) => ( + + + {groupName} + - {item.shortcut ? ( - ( + handleSelect(item)} + style={({ pressed }) => ({ + minHeight: 48, + borderRadius: radii.xl, + paddingVertical: spacing.md, + paddingHorizontal: spacing.md, + flexDirection: "row", + alignItems: "center", + gap: spacing.md, + backgroundColor: pressed + ? colors.backgroundMuted + : colors.surfaceMuted, borderWidth: 1, - borderColor: colors.borderMuted, - }} + borderColor: pressed ? colors.border : colors.borderMuted, + opacity: item.disabled ? 0.5 : 1, + })} > - - {item.shortcut} - - - ) : null} - - ))} - - )) - )} - - - + {item.icon ? renderIcon(item.icon, colors.primary, 18) : null} + + + + {item.label} + + {item.description ? ( + + {item.description} + + ) : null} + + + {item.badge ? ( + + {item.badge} + + ) : null} + + {item.shortcut ? ( + + + {item.shortcut} + + + ) : null} + + ))} + + )) + )} + + + + + ); } diff --git a/packages/rn-ui/src/components/Sheet.tsx b/packages/rn-ui/src/components/Sheet.tsx deleted file mode 100644 index 2bdd5df..0000000 --- a/packages/rn-ui/src/components/Sheet.tsx +++ /dev/null @@ -1,346 +0,0 @@ -import React, { useEffect, useState } from "react"; -import { - Modal, - Pressable, - View, - type ModalProps, - type StyleProp, - type TextStyle, - type ViewProps, - type ViewStyle, -} from "react-native"; -import Animated, { - Easing, - useAnimatedStyle, - useSharedValue, - withTiming, -} from "react-native-reanimated"; -import { scheduleOnRN } from "react-native-worklets"; -import { useSafeAreaInsets } from "react-native-safe-area-context"; - -import { useTheme } from "../theme"; -import { triggerHaptic } from "../utils/haptics"; -import { Text } from "./Text"; -import { - renderIcon, - type RenderIcon, - type BaseGlassProps, - type BaseHapticProps, -} from "./types"; - -export interface SheetProps extends BaseGlassProps, BaseHapticProps { - visible: boolean; - onClose?: () => void; - title?: React.ReactNode; - description?: React.ReactNode; - children?: React.ReactNode; - closeIcon?: RenderIcon; - dismissOnBackdropPress?: boolean; - animated?: boolean; - animationDuration?: number; - modalProps?: Omit< - ModalProps, - "visible" | "transparent" | "animationType" | "onRequestClose" - >; - overlayStyle?: StyleProp; - style?: StyleProp; - contentStyle?: StyleProp; - titleStyle?: StyleProp; - descriptionStyle?: StyleProp; -} - -export function Sheet({ - visible, - onClose, - title, - description, - children, - glass = false, - haptic = true, - closeIcon, - dismissOnBackdropPress = true, - animated = true, - animationDuration = 240, - modalProps, - overlayStyle, - style, - contentStyle, - titleStyle, - descriptionStyle, -}: SheetProps) { - const { colors, components, radii, spacing, isDark } = useTheme(); - const insets = useSafeAreaInsets(); - const [mounted, setMounted] = useState(visible); - const progress = useSharedValue(0); - - const bottomInset = Math.max(insets.bottom, spacing.md); - - useEffect(() => { - if (visible) { - if (haptic) triggerHaptic("medium"); - progress.value = 0; // Guarantee 0 offset on initial mount frame (no flicker/glitch) - setMounted(true); - if (animated) { - progress.value = withTiming(1, { - duration: animationDuration, - easing: Easing.out(Easing.quad), - }); - } else { - progress.value = 1; - } - } else { - if (animated) { - progress.value = withTiming( - 0, - { - duration: Math.max(140, animationDuration - 60), - easing: Easing.in(Easing.quad), - }, - (finished) => { - if (finished) { - scheduleOnRN(setMounted, false); - } - }, - ); - } else { - progress.value = 0; - setMounted(false); - } - } - }, [animated, animationDuration, haptic, progress, visible]); - - const backdropAnimatedStyle = useAnimatedStyle(() => ({ - opacity: progress.value, - })); - - const sheetAnimatedStyle = useAnimatedStyle(() => ({ - opacity: progress.value, - transform: [{ translateY: 500 * (1 - progress.value) }], - })); - - if (!mounted) return null; - - const requestClose = () => { - if (haptic) triggerHaptic("selection"); - onClose?.(); - }; - - const handleBackdropPress = () => { - if (dismissOnBackdropPress) { - requestClose(); - } - }; - - const glassBg = isDark - ? "rgba(15, 27, 45, 0.94)" - : "rgba(255, 255, 255, 0.96)"; - - const sheetBg = glass ? glassBg : colors.surface; - const borderColor = glass - ? isDark - ? "rgba(248, 250, 252, 0.20)" - : "rgba(15, 23, 42, 0.14)" - : colors.border; - - return ( - - - - - - - {/* Top Handle Drag Bar */} - - - - - {/* Header with Title & Close Button */} - {title || description || onClose ? ( - - - {title ? ( - typeof title === "string" ? ( - - {title} - - ) : ( - title - ) - ) : null} - - {description ? ( - typeof description === "string" ? ( - - {description} - - ) : ( - description - ) - ) : null} - - - {onClose ? ( - ({ - width: 32, - height: 32, - borderRadius: 16, - alignItems: "center", - justifyContent: "center", - backgroundColor: pressed ? colors.backgroundMuted : "transparent", - opacity: pressed ? 0.8 : 0.7, - })} - > - {closeIcon ? ( - renderIcon(closeIcon, colors.textMuted, 16) - ) : ( - - ✕ - - )} - - ) : null} - - ) : null} - - {/* Main Sheet Body */} - {children ? {children} : null} - - - - - ); -} - -export const SheetContent = View; - -export interface SheetHeaderProps extends ViewProps { - style?: StyleProp; - children?: React.ReactNode; -} - -export function SheetHeader({ style, children }: SheetHeaderProps) { - const { spacing } = useTheme(); - return {children}; -} - -export interface SheetTitleProps { - children?: React.ReactNode; - style?: StyleProp; -} - -export function SheetTitle({ children, style }: SheetTitleProps) { - return ( - - {children} - - ); -} - -export interface SheetDescriptionProps { - children?: React.ReactNode; - style?: StyleProp; -} - -export function SheetDescription({ children, style }: SheetDescriptionProps) { - return ( - - {children} - - ); -} - -export interface SheetFooterProps extends ViewProps { - style?: StyleProp; - children?: React.ReactNode; -} - -export function SheetFooter({ style, children }: SheetFooterProps) { - const { spacing } = useTheme(); - return ( - - {children} - - ); -} diff --git a/packages/rn-ui/src/components/Tabs.tsx b/packages/rn-ui/src/components/Tabs.tsx deleted file mode 100644 index a7717fa..0000000 --- a/packages/rn-ui/src/components/Tabs.tsx +++ /dev/null @@ -1,370 +0,0 @@ -import React from "react"; -import { - Pressable, - View, - type LayoutChangeEvent, - type PressableProps, - type StyleProp, - type ViewProps, - type ViewStyle, -} from "react-native"; -import Animated, { - useAnimatedStyle, - useSharedValue, - withTiming, -} from "react-native-reanimated"; - -import { useTheme } from "../theme"; -import { triggerHaptic } from "../utils/haptics"; -import { Text } from "./Text"; -import { - renderIcon, - type RenderIcon, - type BaseAnimatedProps, - type BaseGlassProps, - type BaseHapticProps, - type SizeProps, - type VariantProps, -} from "./types"; - -export type TabsVariant = "segmented" | "pills" | "underline" | "soft"; -export type TabsSize = "sm" | "md" | "lg"; - -export interface TabNodeLayout { - x: number; - width: number; - height: number; -} - -export interface TabsContextValue { - value?: string; - onValueChange?: (value: string) => void; - variant: TabsVariant; - size: TabsSize; - glass: boolean; - haptic: boolean; - animated: boolean; - registerTabNode: (value: string, layout: TabNodeLayout) => void; - tabNodes: Record; -} - -const TabsContext = React.createContext(null); - -export function useTabsContext() { - const context = React.useContext(TabsContext); - if (!context) { - throw new Error("Tabs compound components must be rendered inside "); - } - return context; -} - -export interface TabsProps - extends ViewProps, - BaseGlassProps, - BaseHapticProps, - BaseAnimatedProps, - VariantProps, - SizeProps { - value?: string; - defaultValue?: string; - onValueChange?: (value: string) => void; - style?: StyleProp; -} - -export function Tabs({ - value, - defaultValue, - variant = "segmented", - size = "md", - glass = false, - haptic = true, - animated = true, - onValueChange, - style, - ...props -}: TabsProps) { - const { spacing } = useTheme(); - const [internalValue, setInternalValue] = React.useState(defaultValue); - const [tabNodes, setTabNodes] = React.useState>({}); - - const currentValue = value ?? internalValue; - - const registerTabNode = React.useCallback( - (tabValue: string, layout: TabNodeLayout) => { - setTabNodes((prev) => { - if ( - prev[tabValue]?.x === layout.x && - prev[tabValue]?.width === layout.width - ) { - return prev; - } - return { ...prev, [tabValue]: layout }; - }); - }, - [], - ); - - const handleValueChange = React.useCallback( - (next: string) => { - if (haptic) triggerHaptic("selection"); - if (value === undefined) setInternalValue(next); - onValueChange?.(next); - }, - [haptic, onValueChange, value], - ); - - const context = React.useMemo( - () => ({ - value: currentValue, - onValueChange: handleValueChange, - variant, - size, - glass, - haptic, - animated, - registerTabNode, - tabNodes, - }), - [ - currentValue, - handleValueChange, - variant, - size, - glass, - haptic, - animated, - registerTabNode, - tabNodes, - ], - ); - - return ( - - - - ); -} - -const listHeights: Record = { - sm: 36, - md: 44, - lg: 52, -}; - -export interface TabsListProps extends ViewProps { - style?: StyleProp; -} - -export function TabsList({ style, children, ...props }: TabsListProps) { - const { colors, components, radii, spacing, isDark } = useTheme(); - const { value, variant, size, glass, animated, tabNodes } = useTabsContext(); - - const activeNode = value ? tabNodes[value] : undefined; - - const indicatorX = useSharedValue(0); - const indicatorWidth = useSharedValue(0); - const initialized = useSharedValue(false); - - React.useEffect(() => { - if (activeNode && activeNode.width > 0) { - if (!animated || !initialized.value) { - indicatorX.value = activeNode.x; - indicatorWidth.value = activeNode.width; - initialized.value = true; - } else { - indicatorX.value = withTiming(activeNode.x, { duration: 180 }); - indicatorWidth.value = withTiming(activeNode.width, { duration: 180 }); - } - } - }, [activeNode, animated, indicatorX, indicatorWidth, initialized]); - - const glassBg = isDark - ? "rgba(15, 27, 45, 0.70)" - : "rgba(255, 255, 255, 0.85)"; - - const isUnderline = variant === "underline"; - const isPills = variant === "pills"; - - const height = listHeights[size]; - - const indicatorStyle = useAnimatedStyle(() => ({ - transform: [{ translateX: indicatorX.value }], - width: indicatorWidth.value, - })); - - const activeBg = - variant === "segmented" || variant === "pills" - ? colors.primary - : variant === "soft" - ? colors.primarySoft - : colors.transparent; - - const padding = isUnderline ? 0 : 4; - // Concentric radius rule: InnerRadius = OuterRadius - Padding (16 - 4 = 12px) - const innerRadius = isPills - ? radii.full - : isUnderline - ? 0 - : radii.md; - - return ( - - {/* Concentric Sliding Active Tab Indicator */} - {activeNode && ( - - )} - {children} - - ); -} - -export interface TabsTriggerProps extends Omit { - value: string; - icon?: RenderIcon; - children?: React.ReactNode; - style?: StyleProp; -} - -export function TabsTrigger({ - value, - icon, - children, - style, - disabled, - ...props -}: TabsTriggerProps) { - const { - value: selectedValue, - onValueChange, - variant, - size, - registerTabNode, - } = useTabsContext(); - const { colors, radii, spacing } = useTheme(); - const active = selectedValue === value; - - const handleLayout = (event: LayoutChangeEvent) => { - const { x, width, height } = event.nativeEvent.layout; - registerTabNode(value, { x, width, height }); - }; - - const activeTextColor = - variant === "segmented" || variant === "pills" - ? colors.onPrimary - : colors.primary; - - const isPills = variant === "pills"; - const isUnderline = variant === "underline"; - - const triggerRadius = isPills ? radii.full : isUnderline ? 0 : radii.md; - - return ( - onValueChange?.(value)} - style={({ pressed }) => [ - { - flex: 1, - height: isUnderline ? listHeights[size] : "100%", - borderRadius: triggerRadius, - alignItems: "center", - justifyContent: "center", - flexDirection: "row", - gap: spacing.xs, - paddingHorizontal: spacing.sm, - zIndex: 1, - opacity: disabled ? 0.5 : pressed ? 0.82 : 1, - }, - style, - ]} - {...props} - > - {icon - ? renderIcon( - icon, - active ? activeTextColor : colors.textMuted, - size === "sm" ? 14 : 16, - ) - : null} - {typeof children === "string" ? ( - - {children} - - ) : ( - children - )} - - ); -} - -export interface TabsContentProps extends ViewProps { - value: string; - forceMount?: boolean; - style?: StyleProp; -} - -export function TabsContent({ - value, - forceMount = false, - style, - ...props -}: TabsContentProps) { - const { value: selectedValue } = useTabsContext(); - const active = selectedValue === value; - - if (!active && !forceMount) return null; - return ( - - ); -} diff --git a/packages/rn-ui/src/components/index.ts b/packages/rn-ui/src/components/index.ts index 9589630..052b377 100644 --- a/packages/rn-ui/src/components/index.ts +++ b/packages/rn-ui/src/components/index.ts @@ -142,26 +142,6 @@ export type { BreadcrumbProps, BreadcrumbSeparatorProps, } from "./Breadcrumb"; -export { - BottomSheet, - BottomSheetFlatList, - BottomSheetModal, - BottomSheetModalProvider, - BottomSheetScrollView, - BottomSheetSectionList, - BottomSheetTextInput, - BottomSheetView, - useBottomSheet, - useBottomSheetModal, -} from "./BottomSheet"; -export type { - BottomSheetBackdropProps, - BottomSheetMethods, - BottomSheetModalMethods, - BottomSheetModalProps, - BottomSheetProps, - GorhomBottomSheetProps, -} from "./BottomSheet"; export { Button } from "./Button"; export type { ButtonProps, @@ -334,20 +314,6 @@ export type { RadioGroupProps, RadioGroupVariant, } from "./RadioGroup"; -export { - Sheet, - SheetContent, - SheetDescription, - SheetFooter, - SheetHeader, - SheetTitle, -} from "./Sheet"; -export type { - SheetDescriptionProps, - SheetFooterProps, - SheetHeaderProps, - SheetTitleProps, -} from "./Sheet"; export { Skeleton, SkeletonGroup, SkeletonContext } from "./Skeleton"; export type { SkeletonProps, @@ -369,14 +335,6 @@ export { Stepper } from "./Stepper"; export type { StepperProps } from "./Stepper"; export { Switch } from "./Switch"; export type { SwitchProps, SwitchSize, SwitchTone, SwitchVariant } from "./Switch"; -export { Tabs, TabsContent, TabsList, TabsTrigger } from "./Tabs"; -export type { - TabsContentProps, - TabsContextValue, - TabsListProps, - TabsProps, - TabsTriggerProps, -} from "./Tabs"; export { Text } from "./Text"; export type { TextProps } from "./Text"; export { Textarea } from "./Textarea";