feat: turbostarter boilerplate
Production-ready Next.js boilerplate with: - Runtime env validation (fail-fast on missing vars) - Feature-gated config (S3, Stripe, email, OAuth) - Docker + Coolify deployment pipeline - PostgreSQL + pgvector, MinIO S3, Better Auth - TypeScript strict mode (no ignoreBuildErrors) - i18n (en/es), AI modules, billing, monitoring Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
42
packages/ui/mobile/src/components/skeleton.tsx
Normal file
42
packages/ui/mobile/src/components/skeleton.tsx
Normal file
@@ -0,0 +1,42 @@
|
||||
import * as React from "react";
|
||||
import Animated, {
|
||||
useAnimatedStyle,
|
||||
useSharedValue,
|
||||
withRepeat,
|
||||
withSequence,
|
||||
withTiming,
|
||||
} from "react-native-reanimated";
|
||||
|
||||
import { cn } from "@turbostarter/ui";
|
||||
|
||||
const duration = 1000;
|
||||
|
||||
function Skeleton({
|
||||
className,
|
||||
style,
|
||||
...props
|
||||
}: React.ComponentPropsWithoutRef<typeof Animated.View>) {
|
||||
const sv = useSharedValue(1);
|
||||
|
||||
React.useEffect(() => {
|
||||
sv.value = withRepeat(
|
||||
withSequence(withTiming(0.5, { duration }), withTiming(1, { duration })),
|
||||
-1,
|
||||
);
|
||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||
}, []);
|
||||
|
||||
const animatedStyle = useAnimatedStyle(() => ({
|
||||
opacity: sv.value,
|
||||
}));
|
||||
|
||||
return (
|
||||
<Animated.View
|
||||
style={[animatedStyle, style]}
|
||||
className={cn("bg-secondary dark:bg-muted rounded-md", className)}
|
||||
{...props}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
export { Skeleton };
|
||||
Reference in New Issue
Block a user