snapcn UI Core
snap-cn/snap-cn-uiFreeUtility
Shared timeline-fold hook, theme context, and color math for snapcn UI primitives.
Install it
npx shadcn@latest add https://snapcn.dev/r/snap-cn-ui.jsonSource
1import { useCurrentFrame, useVideoConfig } from "remotion";2import type { Step } from "./types";34export function framesFor(5 d: number | { seconds: number },6 fps: number,7): number {8 return typeof d === "number" ? d : Math.round(d.seconds * fps);9}1011export function revealCount(12 localFrame: number,13 fps: number,14 len: number,15 cps: number,16): number {17 const over = (len / cps) * fps;18 if (over <= 0) return len;19 return Math.max(0, Math.min(len, Math.floor((localFrame / over) * len)));20}2122export function clamp01(t: number): number {23 return Math.max(0, Math.min(1, t));24}2526export function revealedText(full: string, count: number): string {27 const c = Math.max(0, Math.min(full.length, Math.floor(count)));28 return full.slice(0, c);29}3031export interface TypewriterOptions {32 cps?: number;33 speed?: number;34 startFrame?: number;35}3637export interface TypewriterState {38 text: string;39 count: number;40 done: boolean;41 typing: boolean;42}4344export function useTypewriter(45 full: string,46 options: TypewriterOptions = {},47): TypewriterState {48 const { cps = 20, speed = 1, startFrame = 0 } = options;49 const frame = useCurrentFrame();50 const { fps } = useVideoConfig();51 const local = frame * speed - startFrame;52 const count = local <= 0 ? 0 : revealCount(local, fps, full.length, cps);53 return {54 text: revealedText(full, count),55 count,56 done: count >= full.length,57 typing: count > 0 && count < full.length,58 };59}6061export function useCurrentState<S extends string>(62 steps: Step<S>[],63 defaultState: S,64 speed = 1,65): S {66 const effectiveFrame = useCurrentFrame() * speed;67 let current = defaultState;68 let bestAt = -Infinity;69 steps.forEach((step) => {70 if (step.at <= effectiveFrame && step.at >= bestAt) {71 bestAt = step.at;72 current = step.state;73 }74 });75 return current;76}7778export function useStateTransition<S extends string>(79 steps: Step<S>[],80 defaultState: S,81 speed = 1,82 defaultDuration = 8,83): { from: S; to: S; progress: number } {84 const effectiveFrame = useCurrentFrame() * speed;85 const started = steps86 .map((step, index) => ({ step, index }))87 .sort((a, b) => a.step.at - b.step.at || a.index - b.index)88 .filter((e) => e.step.at <= effectiveFrame)89 // Same-`at` ties: the later array entry wins and the earlier tied90 // entries never display, so they can never act as a `from` state.91 .filter(92 (e, i, arr) => i === arr.length - 1 || arr[i + 1].step.at !== e.step.at,93 );94// … truncated
What it pulls in
npm packages
