@waveso/ui 0.0.8 → 0.0.9

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/gradient-reveal-text.tsx"],"names":[],"mappings":";;;;AAuBA,IAAM,cAAA,GAAiB;AAAA,EACrB,SAAA;AAAA,EACA,SAAA;AAAA,EACA,SAAA;AAAA,EACA,SAAA;AAAA,EACA;AACF,CAAA;AAeA,SAAS,kBAAA,CAAmB;AAAA,EAC1B,IAAA;AAAA,EACA,QAAA,GAAW,CAAA;AAAA,EACX,MAAA,GAAS,cAAA;AAAA,EACT,WAAA,GAAc,GAAA;AAAA,EACd,YAAA,GAAe,GAAA;AAAA,EACf,UAAA,GAAa,8CAAA;AAAA,EACb,aAAA,GAAgB,GAAA;AAAA,EAChB;AACF,CAAA,EAA4B;AAC1B,EAAA,MAAM,MAAM,KAAA,EAAM;AAClB,EAAA,MAAM,MAAA,GAAS,OAAsB,IAAI,CAAA;AACzC,EAAA,MAAM,OAAA,GAAU,OAAuB,IAAI,CAAA;AAC3C,EAAA,MAAM,WAAA,GAAc,OAAiC,IAAI,CAAA;AAEzD,EAAA,MAAM,CAAC,EAAA,EAAI,KAAK,CAAA,GAAI,SAAS,EAAE,CAAA,EAAG,CAAA,EAAG,CAAA,EAAG,CAAA,EAAG,CAAA,EAAG,GAAA,EAAK,CAAA,EAAG,IAAI,CAAA;AAC1D,EAAA,MAAM,CAAC,QAAA,EAAU,WAAW,CAAA,GAAI,SAAS,KAAK,CAAA;AAC9C,EAAA,MAAM,CAAC,OAAA,EAAS,UAAU,CAAA,GAAI,SAAS,KAAK,CAAA;AAG5C,EAAA,MAAM,YAAY,MAAA,CAAO,EAAE,IAAI,GAAA,EAAK,EAAA,EAAI,KAAK,CAAA;AAC7C,EAAA,MAAM,aAAa,MAAA,CAAO,EAAE,IAAI,GAAA,EAAK,EAAA,EAAI,KAAK,CAAA;AAC9C,EAAA,MAAM,KAAA,GAAQ,OAAe,CAAC,CAAA;AAG9B,EAAA,MAAM,OAAA,GAAU,YAAY,MAAM;AAChC,IAAA,MAAM,KAAK,OAAA,CAAQ,OAAA;AACnB,IAAA,IAAI,CAAC,EAAA,EAAI;AACT,IAAA,MAAM,IAAA,GAAO,GAAG,OAAA,EAAQ;AACxB,IAAA,IAAI,IAAA,CAAK,UAAU,CAAA,EAAG;AAEtB,IAAA,KAAA,CAAM,EAAE,CAAA,EAAG,IAAA,CAAK,CAAA,EAAG,CAAA,EAAG,IAAA,CAAK,CAAA,EAAG,CAAA,EAAG,IAAA,CAAK,KAAA,EAAO,CAAA,EAAG,IAAA,CAAK,QAAQ,CAAA;AAC7D,IAAA,WAAA,CAAY,IAAI,CAAA;AAAA,EAClB,CAAA,EAAG,EAAE,CAAA;AAEL,EAAA,SAAA,CAAU,MAAM;AACd,IAAA,OAAA,EAAQ;AACR,IAAA,QAAA,CAAS,KAAA,EAAO,KAAA,EAAO,IAAA,CAAK,OAAO,CAAA;AAAA,EACrC,CAAA,EAAG,CAAC,IAAA,EAAM,OAAO,CAAC,CAAA;AAGlB,EAAA,MAAM,gBAAA,GAAmB,WAAA,CAAY,CAAC,EAAA,EAAY,EAAA,KAAe;AAC/D,IAAA,MAAM,KAAK,WAAA,CAAY,OAAA;AACvB,IAAA,IAAI,CAAC,EAAA,EAAI;AACT,IAAA,MAAM,KAAA,GAAQ,EAAA,CAAG,CAAA,GAAI,EAAA,GAAK,EAAA,CAAG,CAAA;AAC7B,IAAA,MAAM,KAAA,GAAQ,EAAA,CAAG,CAAA,GAAI,EAAA,GAAK,EAAA,CAAG,CAAA;AAC7B,IAAA,EAAA,CAAG,YAAA,CAAa,IAAA,EAAM,MAAA,CAAO,KAAK,CAAC,CAAA;AACnC,IAAA,EAAA,CAAG,YAAA,CAAa,IAAA,EAAM,MAAA,CAAO,KAAK,CAAC,CAAA;AAAA,EACrC,CAAA,EAAG,CAAC,EAAE,CAAC,CAAA;AAGP,EAAA,SAAA,CAAU,MAAM;AACd,IAAA,IAAI,YAAY,CAAA,EAAG;AAGnB,IAAA,MAAM,QAAQ,CAAA,GAAI,IAAA,CAAK,IAAI,IAAA,EAAO,CAAA,IAAK,WAAW,EAAA,CAAG,CAAA;AAErD,IAAA,MAAM,OAAO,MAAM;AACjB,MAAA,MAAM,MAAM,UAAA,CAAW,OAAA;AACvB,MAAA,MAAM,MAAM,SAAA,CAAU,OAAA;AACtB,MAAA,GAAA,CAAI,EAAA,IAAA,CAAO,GAAA,CAAI,EAAA,GAAK,GAAA,CAAI,EAAA,IAAM,KAAA;AAC9B,MAAA,GAAA,CAAI,EAAA,IAAA,CAAO,GAAA,CAAI,EAAA,GAAK,GAAA,CAAI,EAAA,IAAM,KAAA;AAC9B,MAAA,gBAAA,CAAiB,GAAA,CAAI,EAAA,EAAI,GAAA,CAAI,EAAE,CAAA;AAC/B,MAAA,KAAA,CAAM,OAAA,GAAU,sBAAsB,IAAI,CAAA;AAAA,IAC5C,CAAA;AAEA,IAAA,KAAA,CAAM,OAAA,GAAU,sBAAsB,IAAI,CAAA;AAC1C,IAAA,OAAO,MAAM,oBAAA,CAAqB,KAAA,CAAM,OAAO,CAAA;AAAA,EACjD,CAAA,EAAG,CAAC,QAAA,EAAU,gBAAgB,CAAC,CAAA;AAE/B,EAAA,MAAM,SAAA,GAAY,CAAC,CAAA,KAAuC;AACxD,IAAA,MAAM,MAAM,MAAA,CAAO,OAAA;AACnB,IAAA,IAAI,CAAC,GAAA,EAAK;AACV,IAAA,MAAM,IAAA,GAAO,IAAI,qBAAA,EAAsB;AACvC,IAAA,MAAM,EAAA,GAAA,CAAM,CAAA,CAAE,OAAA,GAAU,IAAA,CAAK,QAAQ,IAAA,CAAK,KAAA;AAC1C,IAAA,MAAM,EAAA,GAAA,CAAM,CAAA,CAAE,OAAA,GAAU,IAAA,CAAK,OAAO,IAAA,CAAK,MAAA;AACzC,IAAA,SAAA,CAAU,OAAA,GAAU,EAAE,EAAA,EAAI,EAAA,EAAG;AAG7B,IAAA,IAAI,YAAY,CAAA,EAAG;AACjB,MAAA,UAAA,CAAW,OAAA,GAAU,EAAE,EAAA,EAAI,EAAA,EAAG;AAC9B,MAAA,gBAAA,CAAiB,IAAI,EAAE,CAAA;AAAA,IACzB;AAAA,EACF,CAAA;AAEA,EAAA,MAAM,gBAAA,GAAmB,CAAC,CAAA,KAAuC;AAE/D,IAAA,MAAM,MAAM,MAAA,CAAO,OAAA;AACnB,IAAA,IAAI,GAAA,EAAK;AACP,MAAA,MAAM,IAAA,GAAO,IAAI,qBAAA,EAAsB;AACvC,MAAA,MAAM,EAAA,GAAA,CAAM,CAAA,CAAE,OAAA,GAAU,IAAA,CAAK,QAAQ,IAAA,CAAK,KAAA;AAC1C,MAAA,MAAM,EAAA,GAAA,CAAM,CAAA,CAAE,OAAA,GAAU,IAAA,CAAK,OAAO,IAAA,CAAK,MAAA;AACzC,MAAA,SAAA,CAAU,OAAA,GAAU,EAAE,EAAA,EAAI,EAAA,EAAG;AAC7B,MAAA,UAAA,CAAW,OAAA,GAAU,EAAE,EAAA,EAAI,EAAA,EAAG;AAC9B,MAAA,gBAAA,CAAiB,IAAI,EAAE,CAAA;AAAA,IACzB;AACA,IAAA,UAAA,CAAW,IAAI,CAAA;AAAA,EACjB,CAAA;AAGA,EAAA,MAAM,UAAA,GAAa,GAAG,CAAA,GAAI,aAAA;AAC1B,EAAA,MAAM,OAAA,GAAU,GAAG,CAAA,GAAI,KAAA;AACvB,EAAA,MAAM,MAAA,GAAS,EAAA,CAAG,CAAA,GAAI,GAAA,GAAM,EAAA,CAAG,CAAA;AAC/B,EAAA,MAAM,MAAA,GAAS,EAAA,CAAG,CAAA,GAAI,GAAA,GAAM,EAAA,CAAG,CAAA;AAG/B,EAAA,MAAM,UAAA,GAAa,QAAQ,GAAG,CAAA,CAAA;AAC9B,EAAA,MAAM,MAAA,GAAS,QAAQ,GAAG,CAAA,CAAA;AAC1B,EAAA,MAAM,QAAA,GAAW,UAAU,GAAG,CAAA,CAAA;AAG9B,EAAA,MAAM,KAAA,GAAQ,MAAA,CAAO,GAAA,CAAI,CAAC,OAAO,CAAA,MAAO;AAAA,IACtC,MAAA,EAAQ,CAAA,EAAI,CAAA,GAAI,IAAA,CAAK,GAAA,CAAI,OAAO,MAAA,GAAS,CAAA,EAAG,CAAC,CAAA,GAAK,GAAG,CAAA,CAAA,CAAA;AAAA,IACrD;AAAA,GACF,CAAE,CAAA;AAEF,EAAA,MAAM,SAAA,GAAY;AAAA,IAChB,QAAA,EAAU,KAAA;AAAA,IACV,UAAA;AAAA,IACA,IAAA,EAAM,MAAA;AAAA,IACN,WAAA,EAAa,OAAA;AAAA,IACb,cAAA,EAAgB,OAAA;AAAA,IAChB,aAAA,EAAe,OAAA;AAAA,IACf,UAAA,EAAY;AAAA,GACd;AAEA,EAAA,uBACE,IAAA;AAAA,IAAC,KAAA;AAAA,IAAA;AAAA,MACC,GAAA,EAAK,MAAA;AAAA,MACL,WAAA,EAAU,sBAAA;AAAA,MACV,KAAA,EAAM,MAAA;AAAA,MACN,OAAA,EAAS,CAAA,EAAG,EAAA,CAAG,CAAC,CAAA,CAAA,EAAI,EAAA,CAAG,CAAC,CAAA,CAAA,EAAI,EAAA,CAAG,CAAC,CAAA,CAAA,EAAI,EAAA,CAAG,CAAC,CAAA,CAAA;AAAA,MACxC,mBAAA,EAAoB,eAAA;AAAA,MACpB,KAAA,EAAM,4BAAA;AAAA,MACN,YAAA,EAAc,gBAAA;AAAA,MACd,YAAA,EAAc,MAAM,UAAA,CAAW,KAAK,CAAA;AAAA,MACpC,WAAA,EAAa,SAAA;AAAA,MACb,SAAA,EAAW,EAAA,CAAG,aAAA,EAAe,SAAS,CAAA;AAAA,MACtC,KAAA,EAAO,EAAE,OAAA,EAAS,QAAA,GAAW,IAAI,CAAA,EAAE;AAAA,MACnC,aAAA,EAAW,IAAA;AAAA,MAEX,QAAA,EAAA;AAAA,wBAAA,IAAA,CAAC,MAAA,EAAA,EACC,QAAA,EAAA;AAAA,0BAAA,GAAA,CAAC,gBAAA,EAAA,EAAe,EAAA,EAAI,UAAA,EAAY,EAAA,EAAG,IAAA,EAAK,EAAA,EAAG,IAAA,EAAK,EAAA,EAAG,MAAA,EAAO,EAAA,EAAG,IAAA,EAC1D,QAAA,EAAA,OAAA,IACC,KAAA,CAAM,GAAA,CAAI,CAAC,CAAA,qBACT,GAAA,CAAC,MAAA,EAAA,EAAoB,MAAA,EAAQ,CAAA,CAAE,MAAA,EAAQ,SAAA,EAAW,CAAA,CAAE,KAAA,EAAA,EAAzC,CAAA,CAAE,MAA8C,CAC5D,CAAA,EACL,CAAA;AAAA,0BAEA,IAAA;AAAA,YAAC,gBAAA;AAAA,YAAA;AAAA,cACC,GAAA,EAAK,WAAA;AAAA,cACL,EAAA,EAAI,QAAA;AAAA,cACJ,aAAA,EAAc,gBAAA;AAAA,cACd,CAAA,EAAG,UAAA;AAAA,cACH,EAAA,EAAI,MAAA;AAAA,cACJ,EAAA,EAAI,MAAA;AAAA,cAEJ,QAAA,EAAA;AAAA,gCAAA,GAAA,CAAC,MAAA,EAAA,EAAK,MAAA,EAAO,IAAA,EAAK,SAAA,EAAU,OAAA,EAAQ,CAAA;AAAA,gCACpC,GAAA,CAAC,MAAA,EAAA,EAAK,MAAA,EAAO,MAAA,EAAO,WAAU,OAAA,EAAQ;AAAA;AAAA;AAAA,WACxC;AAAA,0BAEA,GAAA,CAAC,MAAA,EAAA,EAAK,EAAA,EAAI,MAAA,EACR,QAAA,kBAAA,GAAA;AAAA,YAAC,MAAA;AAAA,YAAA;AAAA,cACC,CAAA,EAAG,EAAA,CAAG,CAAA,GAAI,EAAA,CAAG,CAAA;AAAA,cACb,CAAA,EAAG,EAAA,CAAG,CAAA,GAAI,EAAA,CAAG,CAAA;AAAA,cACb,KAAA,EAAO,GAAG,CAAA,GAAI,CAAA;AAAA,cACd,MAAA,EAAQ,GAAG,CAAA,GAAI,CAAA;AAAA,cACf,IAAA,EAAM,QAAQ,QAAQ,CAAA,CAAA;AAAA;AAAA,WACxB,EACF;AAAA,SAAA,EACF,CAAA;AAAA,wBAGA,GAAA;AAAA,UAAC,MAAA;AAAA,UAAA;AAAA,YACC,GAAA,EAAK,OAAA;AAAA,YACL,CAAA,EAAE,KAAA;AAAA,YACF,CAAA,EAAE,KAAA;AAAA,YACF,UAAA,EAAW,QAAA;AAAA,YACX,gBAAA,EAAiB,SAAA;AAAA,YACjB,SAAA,EAAU,WAAA;AAAA,YACV,OAAO,EAAE,QAAA,EAAU,KAAA,EAAO,UAAA,EAAY,YAAY,QAAA,EAAS;AAAA,YAE1D,QAAA,EAAA;AAAA;AAAA,SACH;AAAA,wBAGA,GAAA;AAAA,UAAC,MAAA;AAAA,UAAA;AAAA,YACC,CAAA,EAAE,KAAA;AAAA,YACF,CAAA,EAAE,KAAA;AAAA,YACF,UAAA,EAAW,QAAA;AAAA,YACX,gBAAA,EAAiB,SAAA;AAAA,YACjB,SAAA,EAAU,sDAAA;AAAA,YACV,KAAA,EAAO;AAAA,cACL,GAAG,SAAA;AAAA,cACH,OAAA,EAAS,UAAU,YAAA,GAAe,WAAA;AAAA,cAClC,UAAA,EAAY;AAAA,aACd;AAAA,YAEC,QAAA,EAAA;AAAA;AAAA,SACH;AAAA,wBAGA,GAAA;AAAA,UAAC,MAAA;AAAA,UAAA;AAAA,YACC,CAAA,EAAE,KAAA;AAAA,YACF,CAAA,EAAE,KAAA;AAAA,YACF,UAAA,EAAW,QAAA;AAAA,YACX,gBAAA,EAAiB,SAAA;AAAA,YACjB,IAAA,EAAM,QAAQ,MAAM,CAAA,CAAA,CAAA;AAAA,YACpB,SAAA,EAAU,WAAA;AAAA,YACV,KAAA,EAAO;AAAA,cACL,GAAG,SAAA;AAAA,cACH,MAAA,EAAQ,QAAQ,UAAU,CAAA,CAAA;AAAA,aAC5B;AAAA,YAEC,QAAA,EAAA;AAAA;AAAA;AACH;AAAA;AAAA,GACF;AAEJ","file":"gradient-reveal-text.js","sourcesContent":["\"use client\"\n\nimport { useRef, useEffect, useState, useId, useCallback } from \"react\"\nimport { cn } from \"./lib/utils\"\n\ninterface GradientRevealTextProps {\n /** The text to display */\n text: string\n /** Spotlight follow speed in seconds. Default: 0 (instant) */\n duration?: number\n /** Gradient colors for the reveal effect. Default: rainbow */\n colors?: string[]\n /** Base stroke opacity when not hovered. Default: 0.3 */\n baseOpacity?: number\n /** Hovered stroke opacity. Default: 0.7 */\n hoverOpacity?: number\n /** Font family for SVG text. Default: Helvetica Neue */\n fontFamily?: string\n /** Spotlight radius multiplier relative to text height. Default: 0.6 */\n spotlightSize?: number\n className?: string\n}\n\nconst DEFAULT_COLORS = [\n \"#eab308\",\n \"#ef4444\",\n \"#3b82f6\",\n \"#06b6d4\",\n \"#8b5cf6\",\n]\n\n/**\n * Large decorative text with a gradient spotlight that follows the cursor.\n *\n * Renders as an SVG that auto-sizes to fit the text with zero padding.\n * The gradient reveal effect activates on hover — a circular spotlight\n * follows the mouse, revealing rainbow-colored strokes beneath.\n *\n * @example\n * ```tsx\n * <GradientRevealText text=\"HELLO\" />\n * <GradientRevealText text=\"BRAND\" colors={[\"#ff0000\", \"#00ff00\"]} />\n * ```\n */\nfunction GradientRevealText({\n text,\n duration = 0,\n colors = DEFAULT_COLORS,\n baseOpacity = 0.3,\n hoverOpacity = 0.7,\n fontFamily = \"Helvetica Neue, Helvetica, Arial, sans-serif\",\n spotlightSize = 0.6,\n className,\n}: GradientRevealTextProps) {\n const uid = useId()\n const svgRef = useRef<SVGSVGElement>(null)\n const textRef = useRef<SVGTextElement>(null)\n const gradientRef = useRef<SVGRadialGradientElement>(null)\n\n const [vb, setVb] = useState({ x: 0, y: 0, w: 100, h: 20 })\n const [measured, setMeasured] = useState(false)\n const [hovered, setHovered] = useState(false)\n\n // Target position (where cursor is) and current animated position\n const targetPos = useRef({ cx: 0.5, cy: 0.5 })\n const currentPos = useRef({ cx: 0.5, cy: 0.5 })\n const rafId = useRef<number>(0)\n\n // Measure text bbox → set viewBox to fit exactly\n const measure = useCallback(() => {\n const el = textRef.current\n if (!el) return\n const bbox = el.getBBox()\n if (bbox.width === 0) return\n\n setVb({ x: bbox.x, y: bbox.y, w: bbox.width, h: bbox.height })\n setMeasured(true)\n }, [])\n\n useEffect(() => {\n measure()\n document.fonts?.ready?.then(measure)\n }, [text, measure])\n\n // Update the SVG gradient attributes directly (no React re-render)\n const applyGradientPos = useCallback((cx: number, cy: number) => {\n const el = gradientRef.current\n if (!el) return\n const svgCx = vb.x + cx * vb.w\n const svgCy = vb.y + cy * vb.h\n el.setAttribute(\"cx\", String(svgCx))\n el.setAttribute(\"cy\", String(svgCy))\n }, [vb])\n\n // RAF loop for smooth follow\n useEffect(() => {\n if (duration <= 0) return\n\n // Lerp factor: higher = faster catch-up. Derived from duration.\n const speed = 1 - Math.pow(0.001, 1 / (duration * 60))\n\n const tick = () => {\n const cur = currentPos.current\n const tgt = targetPos.current\n cur.cx += (tgt.cx - cur.cx) * speed\n cur.cy += (tgt.cy - cur.cy) * speed\n applyGradientPos(cur.cx, cur.cy)\n rafId.current = requestAnimationFrame(tick)\n }\n\n rafId.current = requestAnimationFrame(tick)\n return () => cancelAnimationFrame(rafId.current)\n }, [duration, applyGradientPos])\n\n const updatePos = (e: React.MouseEvent<SVGSVGElement>) => {\n const svg = svgRef.current\n if (!svg) return\n const rect = svg.getBoundingClientRect()\n const cx = (e.clientX - rect.left) / rect.width\n const cy = (e.clientY - rect.top) / rect.height\n targetPos.current = { cx, cy }\n\n // If no smooth follow, apply instantly\n if (duration <= 0) {\n currentPos.current = { cx, cy }\n applyGradientPos(cx, cy)\n }\n }\n\n const handleMouseEnter = (e: React.MouseEvent<SVGSVGElement>) => {\n // Snap to entry point — no lerp on first frame\n const svg = svgRef.current\n if (svg) {\n const rect = svg.getBoundingClientRect()\n const cx = (e.clientX - rect.left) / rect.width\n const cy = (e.clientY - rect.top) / rect.height\n targetPos.current = { cx, cy }\n currentPos.current = { cx, cy }\n applyGradientPos(cx, cy)\n }\n setHovered(true)\n }\n\n // Derived values\n const spotlightR = vb.h * spotlightSize\n const strokeW = vb.h * 0.015\n const initCx = vb.x + 0.5 * vb.w\n const initCy = vb.y + 0.5 * vb.h\n\n // Unique SVG IDs\n const gradientId = `grad-${uid}`\n const maskId = `mask-${uid}`\n const revealId = `reveal-${uid}`\n\n // Evenly distribute color stops\n const stops = colors.map((color, i) => ({\n offset: `${(i / Math.max(colors.length - 1, 1)) * 100}%`,\n color,\n }))\n\n const textStyle = {\n fontSize: \"1em\",\n fontFamily,\n fill: \"none\",\n strokeWidth: strokeW,\n strokeLinejoin: \"round\" as const,\n strokeLinecap: \"round\" as const,\n paintOrder: \"stroke fill\" as const,\n }\n\n return (\n <svg\n ref={svgRef}\n data-slot=\"gradient-reveal-text\"\n width=\"100%\"\n viewBox={`${vb.x} ${vb.y} ${vb.w} ${vb.h}`}\n preserveAspectRatio=\"xMidYMid meet\"\n xmlns=\"http://www.w3.org/2000/svg\"\n onMouseEnter={handleMouseEnter}\n onMouseLeave={() => setHovered(false)}\n onMouseMove={updatePos}\n className={cn(\"select-none\", className)}\n style={{ opacity: measured ? 1 : 0 }}\n aria-hidden\n >\n <defs>\n <linearGradient id={gradientId} x1=\"0%\" y1=\"0%\" x2=\"100%\" y2=\"0%\">\n {hovered &&\n stops.map((s) => (\n <stop key={s.offset} offset={s.offset} stopColor={s.color} />\n ))}\n </linearGradient>\n\n <radialGradient\n ref={gradientRef}\n id={revealId}\n gradientUnits=\"userSpaceOnUse\"\n r={spotlightR}\n cx={initCx}\n cy={initCy}\n >\n <stop offset=\"0%\" stopColor=\"white\" />\n <stop offset=\"100%\" stopColor=\"black\" />\n </radialGradient>\n\n <mask id={maskId}>\n <rect\n x={vb.x - vb.w}\n y={vb.y - vb.h}\n width={vb.w * 3}\n height={vb.h * 3}\n fill={`url(#${revealId})`}\n />\n </mask>\n </defs>\n\n {/* Hidden text for measurement */}\n <text\n ref={textRef}\n x=\"50%\"\n y=\"50%\"\n textAnchor=\"middle\"\n dominantBaseline=\"central\"\n className=\"font-bold\"\n style={{ fontSize: \"1em\", fontFamily, visibility: \"hidden\" }}\n >\n {text}\n </text>\n\n {/* Base stroke — subtle outline */}\n <text\n x=\"50%\"\n y=\"50%\"\n textAnchor=\"middle\"\n dominantBaseline=\"central\"\n className=\"font-bold stroke-neutral-200 dark:stroke-neutral-800\"\n style={{\n ...textStyle,\n opacity: hovered ? hoverOpacity : baseOpacity,\n transition: \"opacity 0.3s ease\",\n }}\n >\n {text}\n </text>\n\n {/* Gradient reveal on hover */}\n <text\n x=\"50%\"\n y=\"50%\"\n textAnchor=\"middle\"\n dominantBaseline=\"central\"\n mask={`url(#${maskId})`}\n className=\"font-bold\"\n style={{\n ...textStyle,\n stroke: `url(#${gradientId})`,\n }}\n >\n {text}\n </text>\n </svg>\n )\n}\n\nexport { GradientRevealText }\nexport type { GradientRevealTextProps }\n"]}
@@ -15,7 +15,7 @@ type InputGroupInputProps = React.ComponentProps<"input">;
15
15
  type InputGroupTextareaProps = React.ComponentProps<"textarea">;
16
16
  declare function InputGroup({ className, ...props }: InputGroupProps): react_jsx_runtime.JSX.Element;
17
17
  declare const inputGroupAddonVariants: (props?: ({
18
- align?: "inline-end" | "inline-start" | "block-start" | "block-end" | null | undefined;
18
+ align?: "inline-end" | "inline-start" | "block-end" | "block-start" | null | undefined;
19
19
  } & class_variance_authority_types.ClassProp) | undefined) => string;
20
20
  declare function InputGroupAddon({ className, align, ...props }: InputGroupAddonProps): react_jsx_runtime.JSX.Element;
21
21
  declare const inputGroupButtonVariants: (props?: ({
@@ -1,5 +1,5 @@
1
- import { Button } from './chunk-OUFYQLVN.js';
2
1
  import { ChevronLeftIcon, ChevronRightIcon, EllipsisIcon } from './chunk-DIGOLJIR.js';
2
+ import { Button } from './chunk-OUFYQLVN.js';
3
3
  import { cn } from './chunk-76UQO56T.js';
4
4
  import { jsx, jsxs } from 'react/jsx-runtime';
5
5
 
package/dist/sidebar.js CHANGED
@@ -1,11 +1,11 @@
1
- import { TooltipProvider, TooltipTrigger, Tooltip, TooltipContent } from './chunk-V4ZX4YCP.js';
2
1
  import { useIsMobile } from './chunk-ZZZH3JGW.js';
2
+ import { TooltipProvider, TooltipTrigger, Tooltip, TooltipContent } from './chunk-V4ZX4YCP.js';
3
3
  import { Skeleton } from './chunk-7F4MPMLJ.js';
4
- import { Drawer, DrawerContent, DrawerHeader, DrawerTitle, DrawerDescription } from './chunk-MO4KRZFJ.js';
4
+ import { Drawer, DrawerContent, DrawerHeader, DrawerTitle, DrawerDescription } from './chunk-BKTJYX4M.js';
5
5
  import { Separator } from './chunk-RPQHL6C5.js';
6
6
  import { Input } from './chunk-QFSEK4M6.js';
7
- import { Button } from './chunk-OUFYQLVN.js';
8
7
  import { SidebarPanelIcon } from './chunk-DIGOLJIR.js';
8
+ import { Button } from './chunk-OUFYQLVN.js';
9
9
  import { cn } from './chunk-76UQO56T.js';
10
10
  import * as React from 'react';
11
11
  import { Collapsible } from '@base-ui/react/collapsible';
@@ -0,0 +1,67 @@
1
+ import * as react_jsx_runtime from 'react/jsx-runtime';
2
+ import { ReactElement } from 'react';
3
+
4
+ interface TextSegment {
5
+ text: string;
6
+ /** Optional className applied to this segment (e.g., bold, colored) */
7
+ className?: string;
8
+ }
9
+ interface TypewriterProps {
10
+ /** Simple string or styled segments for per-word/phrase styling */
11
+ text: string | TextSegment[];
12
+ /** Element to render into. Receives the animated text as children. */
13
+ children: ReactElement;
14
+ /** Time per character in seconds. Default: 0.04 */
15
+ speed?: number;
16
+ /** Delay before typing starts in seconds. Default: 0 */
17
+ delay?: number;
18
+ /** Show a blinking cursor during typing. Default: true */
19
+ cursor?: boolean;
20
+ /** Cursor character. Default: '|' */
21
+ cursorChar?: string;
22
+ /** Trigger when scrolled into view instead of on mount. Default: false */
23
+ onView?: boolean;
24
+ /** Trigger once. Default: true */
25
+ once?: boolean;
26
+ /**
27
+ * Reveal mode. Default: 'typing'
28
+ * - 'typing': character-by-character like someone typing
29
+ * - 'smooth': sliding mask reveal (cinematic feel)
30
+ */
31
+ variant?: "typing" | "smooth";
32
+ /** Duration of the smooth reveal in seconds. Default: 2 */
33
+ smoothDuration?: number;
34
+ }
35
+ /**
36
+ * Text reveal animation with two modes:
37
+ *
38
+ * **typing** (default) — character-by-character like someone typing.
39
+ * **smooth** — cinematic sliding mask reveal.
40
+ *
41
+ * Supports plain strings or styled segments for per-word coloring.
42
+ * Zero wrapper divs — renders into the child element via cloneElement.
43
+ *
44
+ * @example
45
+ * ```tsx
46
+ * // Simple string
47
+ * <Typewriter text="Welcome to the future.">
48
+ * <h1 className="text-4xl font-bold" />
49
+ * </Typewriter>
50
+ *
51
+ * // Per-word styling
52
+ * <Typewriter text={[
53
+ * { text: "Build on " },
54
+ * { text: "Intuition", className: "text-primary font-bold" },
55
+ * ]}>
56
+ * <h1 className="text-4xl" />
57
+ * </Typewriter>
58
+ *
59
+ * // Smooth reveal
60
+ * <Typewriter text="Cinematic reveal." variant="smooth" smoothDuration={1.5}>
61
+ * <h1 className="text-4xl font-bold" />
62
+ * </Typewriter>
63
+ * ```
64
+ */
65
+ declare function Typewriter({ text, children, speed, delay, cursor, cursorChar, onView, once, variant, smoothDuration, }: TypewriterProps): react_jsx_runtime.JSX.Element;
66
+
67
+ export { type TextSegment, Typewriter, type TypewriterProps };
@@ -0,0 +1,198 @@
1
+ import { useRef, useId, useState, useEffect, isValidElement, cloneElement } from 'react';
2
+ import { useInView } from 'motion/react';
3
+ import { jsx, jsxs, Fragment } from 'react/jsx-runtime';
4
+
5
+ function mergeRef(internalRef, externalRef) {
6
+ return (el) => {
7
+ internalRef.current = el;
8
+ if (typeof externalRef === "function") externalRef(el);
9
+ else if (externalRef && typeof externalRef === "object") {
10
+ externalRef.current = el;
11
+ }
12
+ };
13
+ }
14
+ function flattenText(text) {
15
+ if (typeof text === "string") return text;
16
+ return text.map((s) => s.text).join("");
17
+ }
18
+ function buildSegmentContent(segments, charIndex) {
19
+ const nodes = [];
20
+ let remaining = charIndex;
21
+ for (let i = 0; i < segments.length; i++) {
22
+ const seg = segments[i];
23
+ if (remaining <= 0) break;
24
+ const visibleChars = seg.text.slice(0, remaining);
25
+ remaining -= visibleChars.length;
26
+ if (seg.className) {
27
+ nodes.push(
28
+ /* @__PURE__ */ jsx("span", { className: seg.className, children: visibleChars }, i)
29
+ );
30
+ } else {
31
+ nodes.push(visibleChars);
32
+ }
33
+ }
34
+ return nodes;
35
+ }
36
+ function BlinkingCursor({ char, id }) {
37
+ const name = `tw-blink-${id}`;
38
+ return /* @__PURE__ */ jsxs(Fragment, { children: [
39
+ /* @__PURE__ */ jsx(
40
+ "span",
41
+ {
42
+ "aria-hidden": true,
43
+ style: {
44
+ animation: `${name} 0.8s step-end infinite`,
45
+ fontWeight: "normal"
46
+ },
47
+ children: char
48
+ }
49
+ ),
50
+ /* @__PURE__ */ jsx("style", { children: `@keyframes ${name} { 0%, 100% { opacity: 1; } 50% { opacity: 0; } }` })
51
+ ] });
52
+ }
53
+ function Typewriter({
54
+ text,
55
+ children,
56
+ speed = 0.04,
57
+ delay = 0,
58
+ cursor = true,
59
+ cursorChar = "|",
60
+ onView = false,
61
+ once = true,
62
+ variant = "typing",
63
+ smoothDuration = 2
64
+ }) {
65
+ const ref = useRef(null);
66
+ const id = useId().replace(/:/g, "");
67
+ const isInView = useInView(ref, { once, margin: "-50px" });
68
+ const shouldAnimate = onView ? isInView : true;
69
+ if (variant === "smooth") {
70
+ return /* @__PURE__ */ jsx(
71
+ SmoothReveal,
72
+ {
73
+ text,
74
+ elementRef: ref,
75
+ id,
76
+ delay,
77
+ duration: smoothDuration,
78
+ cursor,
79
+ cursorChar,
80
+ shouldAnimate,
81
+ children
82
+ }
83
+ );
84
+ }
85
+ return /* @__PURE__ */ jsx(
86
+ TypingReveal,
87
+ {
88
+ text,
89
+ elementRef: ref,
90
+ id,
91
+ speed,
92
+ delay,
93
+ cursor,
94
+ cursorChar,
95
+ shouldAnimate,
96
+ children
97
+ }
98
+ );
99
+ }
100
+ function TypingReveal({
101
+ text,
102
+ children,
103
+ elementRef,
104
+ id,
105
+ speed,
106
+ delay,
107
+ cursor,
108
+ cursorChar,
109
+ shouldAnimate
110
+ }) {
111
+ const flat = flattenText(text);
112
+ const segments = typeof text === "string" ? [{ text }] : text;
113
+ const [charIndex, setCharIndex] = useState(0);
114
+ const [started, setStarted] = useState(false);
115
+ const [done, setDone] = useState(false);
116
+ useEffect(() => {
117
+ if (!shouldAnimate || started) return;
118
+ const timer = setTimeout(() => setStarted(true), delay * 1e3);
119
+ return () => clearTimeout(timer);
120
+ }, [shouldAnimate, delay, started]);
121
+ useEffect(() => {
122
+ if (!started || done) return;
123
+ if (charIndex >= flat.length) {
124
+ setDone(true);
125
+ return;
126
+ }
127
+ const timer = setTimeout(() => setCharIndex((prev) => prev + 1), speed * 1e3);
128
+ return () => clearTimeout(timer);
129
+ }, [started, charIndex, flat.length, speed, done]);
130
+ if (!isValidElement(children)) return children;
131
+ const childProps = children.props;
132
+ const existingRef = childProps.ref;
133
+ const content = started ? buildSegmentContent(segments, charIndex) : null;
134
+ const showCursor = cursor && started && !done;
135
+ return cloneElement(children, {
136
+ ref: mergeRef(elementRef, existingRef),
137
+ children: /* @__PURE__ */ jsxs(Fragment, { children: [
138
+ content,
139
+ showCursor && /* @__PURE__ */ jsx(BlinkingCursor, { char: cursorChar, id })
140
+ ] })
141
+ });
142
+ }
143
+ function SmoothReveal({
144
+ text,
145
+ children,
146
+ elementRef,
147
+ id,
148
+ delay,
149
+ duration,
150
+ cursor,
151
+ cursorChar,
152
+ shouldAnimate
153
+ }) {
154
+ const segments = typeof text === "string" ? [{ text }] : text;
155
+ const [revealed, setRevealed] = useState(false);
156
+ const [done, setDone] = useState(false);
157
+ useEffect(() => {
158
+ if (!shouldAnimate || revealed) return;
159
+ const timer = setTimeout(() => setRevealed(true), delay * 1e3);
160
+ return () => clearTimeout(timer);
161
+ }, [shouldAnimate, delay, revealed]);
162
+ useEffect(() => {
163
+ if (!revealed) return;
164
+ const timer = setTimeout(() => setDone(true), duration * 1e3);
165
+ return () => clearTimeout(timer);
166
+ }, [revealed, duration]);
167
+ if (!isValidElement(children)) return children;
168
+ const childProps = children.props;
169
+ const existingRef = childProps.ref;
170
+ const existingStyle = childProps.style ?? {};
171
+ const animName = `tw-smooth-${id}`;
172
+ const showCursor = cursor && revealed && !done;
173
+ const fullContent = segments.map(
174
+ (seg, i) => seg.className ? /* @__PURE__ */ jsx("span", { className: seg.className, children: seg.text }, i) : seg.text
175
+ );
176
+ return cloneElement(children, {
177
+ ref: mergeRef(elementRef, existingRef),
178
+ style: {
179
+ ...existingStyle,
180
+ whiteSpace: "nowrap",
181
+ overflow: "hidden",
182
+ ...revealed ? {
183
+ animation: `${animName} ${duration}s linear forwards`
184
+ } : {
185
+ maxWidth: 0
186
+ }
187
+ },
188
+ children: /* @__PURE__ */ jsxs(Fragment, { children: [
189
+ /* @__PURE__ */ jsx("style", { children: `@keyframes ${animName} { from { max-width: 0; } to { max-width: 100%; } }` }),
190
+ fullContent,
191
+ showCursor && /* @__PURE__ */ jsx(BlinkingCursor, { char: cursorChar, id })
192
+ ] })
193
+ });
194
+ }
195
+
196
+ export { Typewriter };
197
+ //# sourceMappingURL=typewriter.js.map
198
+ //# sourceMappingURL=typewriter.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/typewriter.tsx"],"names":[],"mappings":";;;;AAoDA,SAAS,QAAA,CACP,aACA,WAAA,EACA;AACA,EAAA,OAAO,CAAC,EAAA,KAA2B;AAChC,IAAC,YAAgD,OAAA,GAAU,EAAA;AAC5D,IAAA,IAAI,OAAO,WAAA,KAAgB,UAAA,EAAY,WAAA,CAAY,EAAE,CAAA;AAAA,SAAA,IAC5C,WAAA,IAAe,OAAO,WAAA,KAAgB,QAAA,EAAU;AACtD,MAAC,YAAgD,OAAA,GAAU,EAAA;AAAA,IAC9D;AAAA,EACF,CAAA;AACF;AAGA,SAAS,YAAY,IAAA,EAAsC;AACzD,EAAA,IAAI,OAAO,IAAA,KAAS,QAAA,EAAU,OAAO,IAAA;AACrC,EAAA,OAAO,IAAA,CAAK,IAAI,CAAC,CAAA,KAAM,EAAE,IAAI,CAAA,CAAE,KAAK,EAAE,CAAA;AACxC;AAGA,SAAS,mBAAA,CACP,UACA,SAAA,EACmB;AACnB,EAAA,MAAM,QAA2B,EAAC;AAClC,EAAA,IAAI,SAAA,GAAY,SAAA;AAEhB,EAAA,KAAA,IAAS,CAAA,GAAI,CAAA,EAAG,CAAA,GAAI,QAAA,CAAS,QAAQ,CAAA,EAAA,EAAK;AACxC,IAAA,MAAM,GAAA,GAAM,SAAS,CAAC,CAAA;AACtB,IAAA,IAAI,aAAa,CAAA,EAAG;AAEpB,IAAA,MAAM,YAAA,GAAe,GAAA,CAAI,IAAA,CAAK,KAAA,CAAM,GAAG,SAAS,CAAA;AAChD,IAAA,SAAA,IAAa,YAAA,CAAa,MAAA;AAE1B,IAAA,IAAI,IAAI,SAAA,EAAW;AACjB,MAAA,KAAA,CAAM,IAAA;AAAA,4BACH,MAAA,EAAA,EAAa,SAAA,EAAW,GAAA,CAAI,SAAA,EAC1B,0BADQ,CAEX;AAAA,OACF;AAAA,IACF,CAAA,MAAO;AACL,MAAA,KAAA,CAAM,KAAK,YAAY,CAAA;AAAA,IACzB;AAAA,EACF;AAEA,EAAA,OAAO,KAAA;AACT;AAIA,SAAS,cAAA,CAAe,EAAE,IAAA,EAAM,EAAA,EAAG,EAAiC;AAClE,EAAA,MAAM,IAAA,GAAO,YAAY,EAAE,CAAA,CAAA;AAC3B,EAAA,uBACE,IAAA,CAAA,QAAA,EAAA,EACE,QAAA,EAAA;AAAA,oBAAA,GAAA;AAAA,MAAC,MAAA;AAAA,MAAA;AAAA,QACC,aAAA,EAAW,IAAA;AAAA,QACX,KAAA,EAAO;AAAA,UACL,SAAA,EAAW,GAAG,IAAI,CAAA,uBAAA,CAAA;AAAA,UAClB,UAAA,EAAY;AAAA,SACd;AAAA,QAEC,QAAA,EAAA;AAAA;AAAA,KACH;AAAA,oBACA,GAAA,CAAC,OAAA,EAAA,EAAO,QAAA,EAAA,CAAA,WAAA,EAAc,IAAI,CAAA,iDAAA,CAAA,EAAoD;AAAA,GAAA,EAChF,CAAA;AAEJ;AAkCA,SAAS,UAAA,CAAW;AAAA,EAClB,IAAA;AAAA,EACA,QAAA;AAAA,EACA,KAAA,GAAQ,IAAA;AAAA,EACR,KAAA,GAAQ,CAAA;AAAA,EACR,MAAA,GAAS,IAAA;AAAA,EACT,UAAA,GAAa,GAAA;AAAA,EACb,MAAA,GAAS,KAAA;AAAA,EACT,IAAA,GAAO,IAAA;AAAA,EACP,OAAA,GAAU,QAAA;AAAA,EACV,cAAA,GAAiB;AACnB,CAAA,EAAoB;AAClB,EAAA,MAAM,GAAA,GAAM,OAAoB,IAAI,CAAA;AACpC,EAAA,MAAM,EAAA,GAAK,KAAA,EAAM,CAAE,OAAA,CAAQ,MAAM,EAAE,CAAA;AACnC,EAAA,MAAM,WAAW,SAAA,CAAU,GAAA,EAAK,EAAE,IAAA,EAAM,MAAA,EAAQ,SAAS,CAAA;AAEzD,EAAA,MAAM,aAAA,GAAgB,SAAS,QAAA,GAAW,IAAA;AAE1C,EAAA,IAAI,YAAY,QAAA,EAAU;AACxB,IAAA,uBACE,GAAA;AAAA,MAAC,YAAA;AAAA,MAAA;AAAA,QACC,IAAA;AAAA,QACA,UAAA,EAAY,GAAA;AAAA,QACZ,EAAA;AAAA,QACA,KAAA;AAAA,QACA,QAAA,EAAU,cAAA;AAAA,QACV,MAAA;AAAA,QACA,UAAA;AAAA,QACA,aAAA;AAAA,QAEC;AAAA;AAAA,KACH;AAAA,EAEJ;AAEA,EAAA,uBACE,GAAA;AAAA,IAAC,YAAA;AAAA,IAAA;AAAA,MACC,IAAA;AAAA,MACA,UAAA,EAAY,GAAA;AAAA,MACZ,EAAA;AAAA,MACA,KAAA;AAAA,MACA,KAAA;AAAA,MACA,MAAA;AAAA,MACA,UAAA;AAAA,MACA,aAAA;AAAA,MAEC;AAAA;AAAA,GACH;AAEJ;AAIA,SAAS,YAAA,CAAa;AAAA,EACpB,IAAA;AAAA,EACA,QAAA;AAAA,EACA,UAAA;AAAA,EACA,EAAA;AAAA,EACA,KAAA;AAAA,EACA,KAAA;AAAA,EACA,MAAA;AAAA,EACA,UAAA;AAAA,EACA;AACF,CAAA,EAUG;AACD,EAAA,MAAM,IAAA,GAAO,YAAY,IAAI,CAAA;AAC7B,EAAA,MAAM,QAAA,GAAW,OAAO,IAAA,KAAS,QAAA,GAAW,CAAC,EAAE,IAAA,EAAM,CAAA,GAAI,IAAA;AACzD,EAAA,MAAM,CAAC,SAAA,EAAW,YAAY,CAAA,GAAI,SAAS,CAAC,CAAA;AAC5C,EAAA,MAAM,CAAC,OAAA,EAAS,UAAU,CAAA,GAAI,SAAS,KAAK,CAAA;AAC5C,EAAA,MAAM,CAAC,IAAA,EAAM,OAAO,CAAA,GAAI,SAAS,KAAK,CAAA;AAEtC,EAAA,SAAA,CAAU,MAAM;AACd,IAAA,IAAI,CAAC,iBAAiB,OAAA,EAAS;AAC/B,IAAA,MAAM,QAAQ,UAAA,CAAW,MAAM,WAAW,IAAI,CAAA,EAAG,QAAQ,GAAI,CAAA;AAC7D,IAAA,OAAO,MAAM,aAAa,KAAK,CAAA;AAAA,EACjC,CAAA,EAAG,CAAC,aAAA,EAAe,KAAA,EAAO,OAAO,CAAC,CAAA;AAElC,EAAA,SAAA,CAAU,MAAM;AACd,IAAA,IAAI,CAAC,WAAW,IAAA,EAAM;AACtB,IAAA,IAAI,SAAA,IAAa,KAAK,MAAA,EAAQ;AAC5B,MAAA,OAAA,CAAQ,IAAI,CAAA;AACZ,MAAA;AAAA,IACF;AACA,IAAA,MAAM,KAAA,GAAQ,UAAA,CAAW,MAAM,YAAA,CAAa,CAAC,SAAS,IAAA,GAAO,CAAC,CAAA,EAAG,KAAA,GAAQ,GAAI,CAAA;AAC7E,IAAA,OAAO,MAAM,aAAa,KAAK,CAAA;AAAA,EACjC,CAAA,EAAG,CAAC,OAAA,EAAS,SAAA,EAAW,KAAK,MAAA,EAAQ,KAAA,EAAO,IAAI,CAAC,CAAA;AAEjD,EAAA,IAAI,CAAC,cAAA,CAAe,QAAQ,CAAA,EAAG,OAAO,QAAA;AAEtC,EAAA,MAAM,aAAa,QAAA,CAAS,KAAA;AAC5B,EAAA,MAAM,cAAe,UAAA,CAA0C,GAAA;AAE/D,EAAA,MAAM,OAAA,GAAU,OAAA,GAAU,mBAAA,CAAoB,QAAA,EAAU,SAAS,CAAA,GAAI,IAAA;AACrE,EAAA,MAAM,UAAA,GAAa,MAAA,IAAU,OAAA,IAAW,CAAC,IAAA;AAEzC,EAAA,OAAO,aAAa,QAAA,EAAU;AAAA,IAC5B,GAAA,EAAK,QAAA,CAAS,UAAA,EAAY,WAAW,CAAA;AAAA,IACrC,0BACE,IAAA,CAAA,QAAA,EAAA,EACG,QAAA,EAAA;AAAA,MAAA,OAAA;AAAA,MACA,UAAA,oBAAc,GAAA,CAAC,cAAA,EAAA,EAAe,IAAA,EAAM,YAAY,EAAA,EAAQ;AAAA,KAAA,EAC3D;AAAA,GAEwB,CAAA;AAC9B;AAIA,SAAS,YAAA,CAAa;AAAA,EACpB,IAAA;AAAA,EACA,QAAA;AAAA,EACA,UAAA;AAAA,EACA,EAAA;AAAA,EACA,KAAA;AAAA,EACA,QAAA;AAAA,EACA,MAAA;AAAA,EACA,UAAA;AAAA,EACA;AACF,CAAA,EAUG;AACD,EAAA,MAAM,QAAA,GAAW,OAAO,IAAA,KAAS,QAAA,GAAW,CAAC,EAAE,IAAA,EAAM,CAAA,GAAI,IAAA;AACzD,EAAA,MAAM,CAAC,QAAA,EAAU,WAAW,CAAA,GAAI,SAAS,KAAK,CAAA;AAC9C,EAAA,MAAM,CAAC,IAAA,EAAM,OAAO,CAAA,GAAI,SAAS,KAAK,CAAA;AAEtC,EAAA,SAAA,CAAU,MAAM;AACd,IAAA,IAAI,CAAC,iBAAiB,QAAA,EAAU;AAChC,IAAA,MAAM,QAAQ,UAAA,CAAW,MAAM,YAAY,IAAI,CAAA,EAAG,QAAQ,GAAI,CAAA;AAC9D,IAAA,OAAO,MAAM,aAAa,KAAK,CAAA;AAAA,EACjC,CAAA,EAAG,CAAC,aAAA,EAAe,KAAA,EAAO,QAAQ,CAAC,CAAA;AAEnC,EAAA,SAAA,CAAU,MAAM;AACd,IAAA,IAAI,CAAC,QAAA,EAAU;AACf,IAAA,MAAM,QAAQ,UAAA,CAAW,MAAM,QAAQ,IAAI,CAAA,EAAG,WAAW,GAAI,CAAA;AAC7D,IAAA,OAAO,MAAM,aAAa,KAAK,CAAA;AAAA,EACjC,CAAA,EAAG,CAAC,QAAA,EAAU,QAAQ,CAAC,CAAA;AAEvB,EAAA,IAAI,CAAC,cAAA,CAAe,QAAQ,CAAA,EAAG,OAAO,QAAA;AAEtC,EAAA,MAAM,aAAa,QAAA,CAAS,KAAA;AAC5B,EAAA,MAAM,cAAe,UAAA,CAA0C,GAAA;AAC/D,EAAA,MAAM,aAAA,GAAiB,UAAA,CAAW,KAAA,IAAS,EAAC;AAE5C,EAAA,MAAM,QAAA,GAAW,aAAa,EAAE,CAAA,CAAA;AAChC,EAAA,MAAM,UAAA,GAAa,MAAA,IAAU,QAAA,IAAY,CAAC,IAAA;AAG1C,EAAA,MAAM,cAAc,QAAA,CAAS,GAAA;AAAA,IAAI,CAAC,GAAA,EAAK,CAAA,KACrC,GAAA,CAAI,4BACF,GAAA,CAAC,MAAA,EAAA,EAAa,SAAA,EAAW,GAAA,CAAI,SAAA,EAC1B,QAAA,EAAA,GAAA,CAAI,IAAA,EAAA,EADI,CAEX,IAEA,GAAA,CAAI;AAAA,GAER;AAEA,EAAA,OAAO,aAAa,QAAA,EAAU;AAAA,IAC5B,GAAA,EAAK,QAAA,CAAS,UAAA,EAAY,WAAW,CAAA;AAAA,IACrC,KAAA,EAAO;AAAA,MACL,GAAG,aAAA;AAAA,MACH,UAAA,EAAY,QAAA;AAAA,MACZ,QAAA,EAAU,QAAA;AAAA,MACV,GAAI,QAAA,GACA;AAAA,QACE,SAAA,EAAW,CAAA,EAAG,QAAQ,CAAA,CAAA,EAAI,QAAQ,CAAA,iBAAA;AAAA,OACpC,GACA;AAAA,QACE,QAAA,EAAU;AAAA;AACZ,KACN;AAAA,IACA,0BACE,IAAA,CAAA,QAAA,EAAA,EACE,QAAA,EAAA;AAAA,sBAAA,GAAA,CAAC,OAAA,EAAA,EAAO,QAAA,EAAA,CAAA,WAAA,EAAc,QAAQ,CAAA,mDAAA,CAAA,EAAsD,CAAA;AAAA,MACnF,WAAA;AAAA,MACA,UAAA,oBAAc,GAAA,CAAC,cAAA,EAAA,EAAe,IAAA,EAAM,YAAY,EAAA,EAAQ;AAAA,KAAA,EAC3D;AAAA,GAEwB,CAAA;AAC9B","file":"typewriter.js","sourcesContent":["\"use client\"\n\nimport {\n type CSSProperties,\n type ReactElement,\n type Ref,\n cloneElement,\n isValidElement,\n useEffect,\n useId,\n useRef,\n useState,\n} from \"react\"\nimport { useInView } from \"motion/react\"\n\n// ── Types ────────────────────────────────────────────────────────────\n\ninterface TextSegment {\n text: string\n /** Optional className applied to this segment (e.g., bold, colored) */\n className?: string\n}\n\ninterface TypewriterProps {\n /** Simple string or styled segments for per-word/phrase styling */\n text: string | TextSegment[]\n /** Element to render into. Receives the animated text as children. */\n children: ReactElement\n /** Time per character in seconds. Default: 0.04 */\n speed?: number\n /** Delay before typing starts in seconds. Default: 0 */\n delay?: number\n /** Show a blinking cursor during typing. Default: true */\n cursor?: boolean\n /** Cursor character. Default: '|' */\n cursorChar?: string\n /** Trigger when scrolled into view instead of on mount. Default: false */\n onView?: boolean\n /** Trigger once. Default: true */\n once?: boolean\n /**\n * Reveal mode. Default: 'typing'\n * - 'typing': character-by-character like someone typing\n * - 'smooth': sliding mask reveal (cinematic feel)\n */\n variant?: \"typing\" | \"smooth\"\n /** Duration of the smooth reveal in seconds. Default: 2 */\n smoothDuration?: number\n}\n\n// ── Helpers ──────────────────────────────────────────────────────────\n\nfunction mergeRef(\n internalRef: React.RefObject<HTMLElement | null>,\n externalRef?: Ref<HTMLElement>,\n) {\n return (el: HTMLElement | null) => {\n ;(internalRef as { current: HTMLElement | null }).current = el\n if (typeof externalRef === \"function\") externalRef(el)\n else if (externalRef && typeof externalRef === \"object\") {\n ;(externalRef as { current: HTMLElement | null }).current = el\n }\n }\n}\n\n/** Flatten text prop into a single string for character counting */\nfunction flattenText(text: string | TextSegment[]): string {\n if (typeof text === \"string\") return text\n return text.map((s) => s.text).join(\"\")\n}\n\n/** Build rendered content from segments up to a character index */\nfunction buildSegmentContent(\n segments: TextSegment[],\n charIndex: number,\n): React.ReactNode[] {\n const nodes: React.ReactNode[] = []\n let remaining = charIndex\n\n for (let i = 0; i < segments.length; i++) {\n const seg = segments[i]!\n if (remaining <= 0) break\n\n const visibleChars = seg.text.slice(0, remaining)\n remaining -= visibleChars.length\n\n if (seg.className) {\n nodes.push(\n <span key={i} className={seg.className}>\n {visibleChars}\n </span>,\n )\n } else {\n nodes.push(visibleChars)\n }\n }\n\n return nodes\n}\n\n// ── Cursor ───────────────────────────────────────────────────────────\n\nfunction BlinkingCursor({ char, id }: { char: string; id: string }) {\n const name = `tw-blink-${id}`\n return (\n <>\n <span\n aria-hidden\n style={{\n animation: `${name} 0.8s step-end infinite`,\n fontWeight: \"normal\",\n }}\n >\n {char}\n </span>\n <style>{`@keyframes ${name} { 0%, 100% { opacity: 1; } 50% { opacity: 0; } }`}</style>\n </>\n )\n}\n\n// ── Typewriter ───────────────────────────────────────────────────────\n\n/**\n * Text reveal animation with two modes:\n *\n * **typing** (default) — character-by-character like someone typing.\n * **smooth** — cinematic sliding mask reveal.\n *\n * Supports plain strings or styled segments for per-word coloring.\n * Zero wrapper divs — renders into the child element via cloneElement.\n *\n * @example\n * ```tsx\n * // Simple string\n * <Typewriter text=\"Welcome to the future.\">\n * <h1 className=\"text-4xl font-bold\" />\n * </Typewriter>\n *\n * // Per-word styling\n * <Typewriter text={[\n * { text: \"Build on \" },\n * { text: \"Intuition\", className: \"text-primary font-bold\" },\n * ]}>\n * <h1 className=\"text-4xl\" />\n * </Typewriter>\n *\n * // Smooth reveal\n * <Typewriter text=\"Cinematic reveal.\" variant=\"smooth\" smoothDuration={1.5}>\n * <h1 className=\"text-4xl font-bold\" />\n * </Typewriter>\n * ```\n */\nfunction Typewriter({\n text,\n children,\n speed = 0.04,\n delay = 0,\n cursor = true,\n cursorChar = \"|\",\n onView = false,\n once = true,\n variant = \"typing\",\n smoothDuration = 2,\n}: TypewriterProps) {\n const ref = useRef<HTMLElement>(null)\n const id = useId().replace(/:/g, \"\")\n const isInView = useInView(ref, { once, margin: \"-50px\" })\n\n const shouldAnimate = onView ? isInView : true\n\n if (variant === \"smooth\") {\n return (\n <SmoothReveal\n text={text}\n elementRef={ref}\n id={id}\n delay={delay}\n duration={smoothDuration}\n cursor={cursor}\n cursorChar={cursorChar}\n shouldAnimate={shouldAnimate}\n >\n {children}\n </SmoothReveal>\n )\n }\n\n return (\n <TypingReveal\n text={text}\n elementRef={ref}\n id={id}\n speed={speed}\n delay={delay}\n cursor={cursor}\n cursorChar={cursorChar}\n shouldAnimate={shouldAnimate}\n >\n {children}\n </TypingReveal>\n )\n}\n\n// ── Typing Reveal ────────────────────────────────────────────────────\n\nfunction TypingReveal({\n text,\n children,\n elementRef,\n id,\n speed,\n delay,\n cursor,\n cursorChar,\n shouldAnimate,\n}: {\n text: string | TextSegment[]\n children: ReactElement\n elementRef: React.RefObject<HTMLElement | null>\n id: string\n speed: number\n delay: number\n cursor: boolean\n cursorChar: string\n shouldAnimate: boolean\n}) {\n const flat = flattenText(text)\n const segments = typeof text === \"string\" ? [{ text }] : text\n const [charIndex, setCharIndex] = useState(0)\n const [started, setStarted] = useState(false)\n const [done, setDone] = useState(false)\n\n useEffect(() => {\n if (!shouldAnimate || started) return\n const timer = setTimeout(() => setStarted(true), delay * 1000)\n return () => clearTimeout(timer)\n }, [shouldAnimate, delay, started])\n\n useEffect(() => {\n if (!started || done) return\n if (charIndex >= flat.length) {\n setDone(true)\n return\n }\n const timer = setTimeout(() => setCharIndex((prev) => prev + 1), speed * 1000)\n return () => clearTimeout(timer)\n }, [started, charIndex, flat.length, speed, done])\n\n if (!isValidElement(children)) return children\n\n const childProps = children.props as Record<string, unknown>\n const existingRef = (childProps as { ref?: Ref<HTMLElement> }).ref\n\n const content = started ? buildSegmentContent(segments, charIndex) : null\n const showCursor = cursor && started && !done\n\n return cloneElement(children, {\n ref: mergeRef(elementRef, existingRef),\n children: (\n <>\n {content}\n {showCursor && <BlinkingCursor char={cursorChar} id={id} />}\n </>\n ),\n } as Record<string, unknown>)\n}\n\n// ── Smooth Reveal ────────────────────────────────────────────────────\n\nfunction SmoothReveal({\n text,\n children,\n elementRef,\n id,\n delay,\n duration,\n cursor,\n cursorChar,\n shouldAnimate,\n}: {\n text: string | TextSegment[]\n children: ReactElement\n elementRef: React.RefObject<HTMLElement | null>\n id: string\n delay: number\n duration: number\n cursor: boolean\n cursorChar: string\n shouldAnimate: boolean\n}) {\n const segments = typeof text === \"string\" ? [{ text }] : text\n const [revealed, setRevealed] = useState(false)\n const [done, setDone] = useState(false)\n\n useEffect(() => {\n if (!shouldAnimate || revealed) return\n const timer = setTimeout(() => setRevealed(true), delay * 1000)\n return () => clearTimeout(timer)\n }, [shouldAnimate, delay, revealed])\n\n useEffect(() => {\n if (!revealed) return\n const timer = setTimeout(() => setDone(true), duration * 1000)\n return () => clearTimeout(timer)\n }, [revealed, duration])\n\n if (!isValidElement(children)) return children\n\n const childProps = children.props as Record<string, unknown>\n const existingRef = (childProps as { ref?: Ref<HTMLElement> }).ref\n const existingStyle = (childProps.style ?? {}) as CSSProperties\n\n const animName = `tw-smooth-${id}`\n const showCursor = cursor && revealed && !done\n\n // Full text is always in the DOM — the mask clip animates to reveal it\n const fullContent = segments.map((seg, i) =>\n seg.className ? (\n <span key={i} className={seg.className}>\n {seg.text}\n </span>\n ) : (\n seg.text\n ),\n )\n\n return cloneElement(children, {\n ref: mergeRef(elementRef, existingRef),\n style: {\n ...existingStyle,\n whiteSpace: \"nowrap\" as const,\n overflow: \"hidden\" as const,\n ...(revealed\n ? {\n animation: `${animName} ${duration}s linear forwards`,\n }\n : {\n maxWidth: 0,\n }),\n },\n children: (\n <>\n <style>{`@keyframes ${animName} { from { max-width: 0; } to { max-width: 100%; } }`}</style>\n {fullContent}\n {showCursor && <BlinkingCursor char={cursorChar} id={id} />}\n </>\n ),\n } as Record<string, unknown>)\n}\n\n// ── Exports ──────────────────────────────────────────────────────────\n\nexport { Typewriter }\nexport type { TypewriterProps, TextSegment }\n"]}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@waveso/ui",
3
- "version": "0.0.8",
3
+ "version": "0.0.9",
4
4
  "description": "Wave UI component library built on Base UI and Tailwind CSS",
5
5
  "type": "module",
6
6
  "sideEffects": [