From 6afc8cf96a31f8685b907552a3a4c9618e9a585f Mon Sep 17 00:00:00 2001 From: Ma'sum Date: Sat, 12 Sep 2026 18:16:24 +0700 Subject: [PATCH 1/2] feat: add Spinner component and optimize Skeleton and Button components --- .../docs/src/content/docs/rn-ui/changelog.mdx | 24 +- .../content/docs/rn-ui/components/index.mdx | 1 + .../content/docs/rn-ui/components/spinner.mdx | 75 ++++ apps/native/app/rn-ui/[id].tsx | 4 + apps/native/app/rn-ui/index.tsx | 7 + apps/native/components/rn-ui/index.ts | 2 +- .../rn-ui/sections/SkeletonSection.tsx | 409 ++++++++++++++++-- .../rn-ui/sections/SpinnerSection.tsx | 277 ++++++++++++ packages/rn-ui/CHANGELOG.md | 40 ++ packages/rn-ui/README.md | 4 +- packages/rn-ui/package.json | 2 +- packages/rn-ui/src/components/Button.tsx | 22 +- packages/rn-ui/src/components/Skeleton.tsx | 166 ++++--- packages/rn-ui/src/components/Spinner.tsx | 405 +++++++++++++++++ packages/rn-ui/src/components/index.ts | 17 +- packages/rn-ui/src/components/types.ts | 22 +- 16 files changed, 1372 insertions(+), 105 deletions(-) create mode 100644 apps/docs/src/content/docs/rn-ui/components/spinner.mdx create mode 100644 apps/native/components/rn-ui/sections/SpinnerSection.tsx create mode 100644 packages/rn-ui/CHANGELOG.md create mode 100644 packages/rn-ui/src/components/Spinner.tsx diff --git a/apps/docs/src/content/docs/rn-ui/changelog.mdx b/apps/docs/src/content/docs/rn-ui/changelog.mdx index ae8a1bd..38b0c00 100644 --- a/apps/docs/src/content/docs/rn-ui/changelog.mdx +++ b/apps/docs/src/content/docs/rn-ui/changelog.mdx @@ -9,10 +9,30 @@ import ChangelogTimeline from '@/components/ChangelogTimeline.astro'; export const releases = [ { - version: 'v0.3.0', - date: '2026-08-25', + version: 'v0.3.1', + date: '2026-09-12', tag: 'Latest Feature Release', isLatest: true, + changes: [ + { + type: 'feat', + text: 'Spinner & Loader Components: Dedicated high-performance loader component with 4 distinct animation variants (circular, ring, dots, bars), customizable speeds, size scales, semantic color tones, and descriptive labels.', + }, + { + type: 'perf', + text: 'Ultra-Lightweight Skeleton with SkeletonGroup: Reduced SharedValues by 75%, removed native onLayout bridge listeners, and added SkeletonGroup for synchronized single-clock multi-skeleton screens with zero layout shift and safe unmount cleanup.', + }, + { + type: 'fix', + text: 'Button & renderIcon Defensive Rendering: Safeguarded mixed children rendering to prevent un-wrapped text string errors in React Native and added automatic support for React forwardRef components.', + }, + ], + }, + { + version: 'v0.3.0', + date: '2026-08-25', + tag: 'Feature Release', + isLatest: false, changes: [ { type: 'feat', diff --git a/apps/docs/src/content/docs/rn-ui/components/index.mdx b/apps/docs/src/content/docs/rn-ui/components/index.mdx index b7b904f..1b5ace6 100644 --- a/apps/docs/src/content/docs/rn-ui/components/index.mdx +++ b/apps/docs/src/content/docs/rn-ui/components/index.mdx @@ -109,4 +109,5 @@ Browse the complete catalog of 63 production-ready React Native & Expo component + diff --git a/apps/docs/src/content/docs/rn-ui/components/spinner.mdx b/apps/docs/src/content/docs/rn-ui/components/spinner.mdx new file mode 100644 index 0000000..f50b9db --- /dev/null +++ b/apps/docs/src/content/docs/rn-ui/components/spinner.mdx @@ -0,0 +1,75 @@ +--- +title: Spinner +description: High-performance animated loading spinner with circular arc, dual-ring, wave dots, and equalizer bar variants. +sidebar: + badge: + text: "New" + variant: "tip" +--- + +import TypeTable from '@/components/TypeTable.astro'; + +The **Spinner** component (also exported as `Loader`) provides fluid 60โ€“120fps UI thread loading indicators with 4 distinct animation variants, semantic design tokens, and optional descriptive labels. + +## Usage + +```tsx +import React from 'react'; +import { Spinner, Box, Card, Text } from '@masumdev/rn-ui'; + +export function SpinnerExample() { + return ( + + {/* 4 Animation Variants */} + + + + + + + + {/* Sizes Scale (xs, sm, default, lg, xl) */} + + + + + + + + + {/* Spinner with Descriptive Label */} + + + ); +} +``` + +## Variants + +- **`circular`**: Classic high-performance SVG arc with background track ring. +- **`ring`**: Elegant dual concentric rings with counter-rotating inner arc. +- **`dots`**: Three phase-shifted pulsing wave dots. +- **`bars`**: Four vertical soundwave equalizer bars. + +## Props API + + diff --git a/apps/native/app/rn-ui/[id].tsx b/apps/native/app/rn-ui/[id].tsx index 375b6df..4338b55 100644 --- a/apps/native/app/rn-ui/[id].tsx +++ b/apps/native/app/rn-ui/[id].tsx @@ -64,6 +64,7 @@ import { SegmentedControlSection, ChipSection, SignaturePadSection, + SpinnerSection, } from "../../components/rn-ui"; export default function RnUiComponentDetailScreen() { @@ -210,6 +211,9 @@ export default function RnUiComponentDetailScreen() { return ; case "signature-pad": return ; + case "spinner": + case "loader": + return ; default: return ; diff --git a/apps/native/app/rn-ui/index.tsx b/apps/native/app/rn-ui/index.tsx index 6245bba..552144f 100644 --- a/apps/native/app/rn-ui/index.tsx +++ b/apps/native/app/rn-ui/index.tsx @@ -431,6 +431,13 @@ export const CATALOG_CATEGORIES: ComponentCategory[] = [ description: "Content loading placeholder animation.", tone: "primary", }, + { + id: "spinner", + name: "Spinner", + description: + "High-performance circular, dots, bars, and dual-ring loader spinners.", + tone: "primary", + }, { id: "avatar", name: "Avatar", diff --git a/apps/native/components/rn-ui/index.ts b/apps/native/components/rn-ui/index.ts index a36b6b7..620b1c4 100644 --- a/apps/native/components/rn-ui/index.ts +++ b/apps/native/components/rn-ui/index.ts @@ -56,4 +56,4 @@ export { SwipeableItemSection } from './sections/SwipeableItemSection'; export { SegmentedControlSection } from './sections/SegmentedControlSection'; export { ChipSection } from './sections/ChipSection'; export { SignaturePadSection } from './sections/SignaturePadSection'; - +export { SpinnerSection } from './sections/SpinnerSection'; diff --git a/apps/native/components/rn-ui/sections/SkeletonSection.tsx b/apps/native/components/rn-ui/sections/SkeletonSection.tsx index f7b40cd..41becdf 100644 --- a/apps/native/components/rn-ui/sections/SkeletonSection.tsx +++ b/apps/native/components/rn-ui/sections/SkeletonSection.tsx @@ -1,51 +1,388 @@ -import { Skeleton, Button, Card, Box, Text } from "@masumdev/rn-ui"; +import { + Avatar, + AvatarBadge, + AvatarFallback, + AvatarImage, + Badge, + Box, + Button, + Card, + Divider, + Skeleton, + SkeletonGroup, + Spinner, + Switch, + Text, +} from "@masumdev/rn-ui"; +import { + Bookmark, + CheckCircle2, + Clock, + RefreshCw, + Share2, + Sparkles, + Star, + Users, +} from "lucide-react-native"; import React from "react"; -import { Section, type RnUiSectionContext } from "../shared"; +import { Image, TouchableOpacity } from "react-native"; +import { SAMPLE_ASSETS, Section, type RnUiSectionContext } from "../shared"; -export function SkeletonSection({ ctx: _ctx }: { ctx: RnUiSectionContext }) { +export function SkeletonSection({ ctx }: { ctx: RnUiSectionContext }) { + const { colors, icon } = ctx; const [animated, setAnimated] = React.useState(true); + // Simulation state + const [isLoading, setIsLoading] = React.useState(false); + const [isFetching, setIsFetching] = React.useState(false); + const [liked, setLiked] = React.useState(false); + const [bookmarked, setBookmarked] = React.useState(false); + + const simulateFetch = React.useCallback(() => { + setIsFetching(true); + setIsLoading(true); + + const timer = setTimeout(() => { + setIsLoading(false); + setIsFetching(false); + }, 2000); + + return () => clearTimeout(timer); + }, []); + return ( -
- - - - Skeleton Shimmer - - + + {/* Existing Skeleton Loaders Section */} +
+ + + + Skeleton Shimmer + + - - - - - - - + + + + + + + + + + + + + - - - - + + + + + Diagonal Shimmer Direction + + + + + Horizontal Shimmer Direction + + + + + +
+ + {/* Interactive Data Transition Simulation Section */} +
+ + + {/* Header & Controls */} + + + Live Content Transition + {isLoading ? ( + + + + Fetching Data... + + + ) : ( + + Live Data Ready + + )} + + + Demonstrating zero layout-shift (CLS) transition from placeholder skeleton to loaded components. + + + {/* Action Buttons / Controls */} + + + + + + Force Skeleton + + { + setIsLoading(val); + if (!val) setIsFetching(false); + }} + tone="primary" + /> + - - - Diagonal Shimmer Direction - + + + {/* Pattern 1: User Profile Card */} + + + 1. USER PROFILE CARD + + + + {isLoading ? ( + /* Skeleton State - Driven by a single shared clock via SkeletonGroup */ + + + + + + + + + + + + - Horizontal Shimmer Direction - + + + + + + {/* Stats pills */} + + + + + + + {/* Actions */} + + + + + + + ) : ( + /* Loaded Data State */ + + + + + {SAMPLE_ASSETS.avatarInitials} + + + + + + + {SAMPLE_ASSETS.avatarName} + + + PRO + + + + @masumrpg ยท Lead Mobile Architect + + + + + + Crafting typed cross-platform design systems, zero-overhead animations, and modular React Native libraries. + + + {/* Stats pills */} + + + 1.4k Followers + + + 63+ Components + + + 890 Stars + + + + {/* Actions */} + + + + + + )} + + + + {/* Pattern 2: Media Feed Card */} + + + 2. MEDIA / FEED ARTICLE CARD + + + + {isLoading ? ( + /* Skeleton State - Driven by SkeletonGroup */ + + + + + + + + + + + + + + + + + + + + + + + + ) : ( + /* Loaded Data State */ + + + + + + REACT NATIVE + + + + + 4 min read + + + + + + Zero-Shift Loading States with Skeleton Shimmer + + + + + + + {SAMPLE_ASSETS.avatarInitials} + + + {"Ma'sum ยท 2h ago"} + + + + + setLiked(!liked)} activeOpacity={0.7}> + + + setBookmarked(!bookmarked)} activeOpacity={0.7}> + + + + + + + + + )} + + - - -
+
+
+ ); } diff --git a/apps/native/components/rn-ui/sections/SpinnerSection.tsx b/apps/native/components/rn-ui/sections/SpinnerSection.tsx new file mode 100644 index 0000000..712ca42 --- /dev/null +++ b/apps/native/components/rn-ui/sections/SpinnerSection.tsx @@ -0,0 +1,277 @@ +import { + Badge, + Box, + Button, + Card, + Spinner, + type SpinnerSize, + type SpinnerSpeed, + type SpinnerVariant, + Text, +} from "@masumdev/rn-ui"; +import { RotateCcw } from "lucide-react-native"; +import React from "react"; +import { Section, type RnUiSectionContext } from "../shared"; + +export function SpinnerSection({ ctx }: { ctx: RnUiSectionContext }) { + const { colors, icon } = ctx; + + const [variant, setVariant] = React.useState("circular"); + const [size, setSize] = React.useState("default"); + const [speed, setSpeed] = React.useState("normal"); + const [animated, setAnimated] = React.useState(true); + + // Card overlay simulation + const [isOverlayLoading, setIsOverlayLoading] = React.useState(false); + + const triggerOverlaySync = () => { + setIsOverlayLoading(true); + setTimeout(() => { + setIsOverlayLoading(false); + }, 2500); + }; + + return ( +
+ + {/* Interactive Playground Card */} + + + + + Live Spinner Playground + + + + + High-performance 60-120fps UI thread loaders with zero memory leaks and safe unmount cleanup. + + + {/* Live Preview Box */} + + + + + {/* Variant Selector */} + + + Variant + + + {(["circular", "ring", "dots", "bars"] as const).map((v) => ( + + ))} + + + + {/* Size Selector */} + + + Size + + + {(["xs", "sm", "default", "lg", "xl"] as const).map((s) => ( + + ))} + + + + {/* Speed Selector */} + + + Speed + + + {(["slow", "normal", "fast"] as const).map((spd) => ( + + ))} + + + + + + {/* All Variants Gallery */} + + + + Loader Variants + + + Four distinct animation styles for different UX context and hierarchies. + + + + + + + Circular Arc + + + + + + + Dual Ring + + + + + + + Wave Dots + + + + + + + Sound Bars + + + + + + + {/* Semantic Tones */} + + + + Semantic Color Tones + + + Inherits full design tokens from the active theme. + + + + {(["primary", "secondary", "accent", "success", "warning", "danger", "info"] as const).map((t) => ( + + + + {t} + + + ))} + + + + + {/* Card Overlay Loading Simulation */} + + + + + Card Overlay Simulation + + + + + + + + Ma'sum Cloud Storage + + + 48 mobile packages, 227 verified clean files, 1.4k followers connected. + + + + Connected + + + v0.3.0 + + + + + {/* Loading Overlay */} + {isOverlayLoading && ( + + + + )} + + + + +
+ ); +} diff --git a/packages/rn-ui/CHANGELOG.md b/packages/rn-ui/CHANGELOG.md new file mode 100644 index 0000000..a85dc93 --- /dev/null +++ b/packages/rn-ui/CHANGELOG.md @@ -0,0 +1,40 @@ +# Changelog + +All notable changes to **@masumdev/rn-ui** will be documented in this file. + +The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), +and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). + +## [0.3.1] - 2026-09-12 + +### Added +- **Spinner & Loader Components**: + - Brand-new dedicated `Spinner` component (also exported as `Loader`) powered by `react-native-reanimated` with 60โ€“120fps UI thread execution. + - Supports 4 visual animation variants: `circular` (high-performance SVG arc), `ring` (dual concentric reverse-spinning rings), `dots` (three phase-shifted pulsing wave dots), and `bars` (vertical soundwave equalizer bars). + - Configurable speeds (`slow`, `normal`, `fast`, or custom ms duration), size scales (`xs`, `sm`, `default`, `lg`, `xl`, or numeric pixel size), semantic color tones (`primary`, `secondary`, `accent`, `success`, `warning`, `danger`, `info`), and optional descriptive labels with flexible placement (`bottom`, `top`, `left`, `right`). + - Added interactive showcase demo in `apps/native` playground with live playground, variant gallery, and card overlay simulation. + - Added complete documentation and prop table in `apps/docs`. + +### Optimized +- **Ultra-Lightweight Skeleton & SkeletonGroup**: + - Reduced memory footprint by 75% (from 4 SharedValues per skeleton to 1 single `progress` SharedValue). + - Cut animation CPU loops by 50% by deriving both subtle pulse and shimmer wave from a unified clock. + - Eliminated `onLayout` bridge round-trips by computing sweep distances geometrically based on viewport width. + - Added `` container to drive arbitrary numbers of child skeletons from a single shared UI thread clock (O(1) overhead regardless of skeleton count). + - Guaranteed safe unmount and destroy cleanup via `cancelAnimation` to prevent worklet collision or frame drops when transitioning from skeleton to live data. + +### Fixed +- **Button Defensive Rendering & renderIcon Safety**: + - Enhanced `