/ components / LogoV2 / AnimatedAsterisk.tsx
AnimatedAsterisk.tsx
 1  import * as React from 'react';
 2  import { useEffect, useRef, useState } from 'react';
 3  import { TEARDROP_ASTERISK } from '../../constants/figures.js';
 4  import { Box, Text, useAnimationFrame } from '../../ink.js';
 5  import { getInitialSettings } from '../../utils/settings/settings.js';
 6  import { hueToRgb, toRGBColor } from '../Spinner/utils.js';
 7  const SWEEP_DURATION_MS = 1500;
 8  const SWEEP_COUNT = 2;
 9  const TOTAL_ANIMATION_MS = SWEEP_DURATION_MS * SWEEP_COUNT;
10  const SETTLED_GREY = toRGBColor({
11    r: 153,
12    g: 153,
13    b: 153
14  });
15  export function AnimatedAsterisk({
16    char = TEARDROP_ASTERISK
17  }: {
18    char?: string;
19  }): React.ReactNode {
20    // Read prefersReducedMotion once at mount — no useSettings() subscription,
21    // since that would re-render whenever settings change.
22    const [reducedMotion] = useState(() => getInitialSettings().prefersReducedMotion ?? false);
23    const [done, setDone] = useState(reducedMotion);
24    // useAnimationFrame's clock is shared — capture our start offset so the
25    // sweep always begins at hue 0 regardless of when we mount.
26    const startTimeRef = useRef<number | null>(null);
27    // Wire the ref so useAnimationFrame's viewport-pause kicks in: if the
28    // user submits a message before the sweep finishes, the clock stops
29    // automatically once this row enters scrollback (prevents flicker).
30    const [ref, time] = useAnimationFrame(done ? null : 50);
31    useEffect(() => {
32      if (done) return;
33      const t = setTimeout(setDone, TOTAL_ANIMATION_MS, true);
34      return () => clearTimeout(t);
35    }, [done]);
36    if (done) {
37      return <Box ref={ref}>
38          <Text color={SETTLED_GREY}>{char}</Text>
39        </Box>;
40    }
41    if (startTimeRef.current === null) {
42      startTimeRef.current = time;
43    }
44    const elapsed = time - startTimeRef.current;
45    const hue = elapsed / SWEEP_DURATION_MS * 360 % 360;
46    return <Box ref={ref}>
47        <Text color={toRGBColor(hueToRgb(hue))}>{char}</Text>
48      </Box>;
49  }
50  //# sourceMappingURL=data:application/json;charset=utf-8;base64,eyJ2ZXJzaW9uIjozLCJuYW1lcyI6WyJSZWFjdCIsInVzZUVmZmVjdCIsInVzZVJlZiIsInVzZVN0YXRlIiwiVEVBUkRST1BfQVNURVJJU0siLCJCb3giLCJUZXh0IiwidXNlQW5pbWF0aW9uRnJhbWUiLCJnZXRJbml0aWFsU2V0dGluZ3MiLCJodWVUb1JnYiIsInRvUkdCQ29sb3IiLCJTV0VFUF9EVVJBVElPTl9NUyIsIlNXRUVQX0NPVU5UIiwiVE9UQUxfQU5JTUFUSU9OX01TIiwiU0VUVExFRF9HUkVZIiwiciIsImciLCJiIiwiQW5pbWF0ZWRBc3RlcmlzayIsImNoYXIiLCJSZWFjdE5vZGUiLCJyZWR1Y2VkTW90aW9uIiwicHJlZmVyc1JlZHVjZWRNb3Rpb24iLCJkb25lIiwic2V0RG9uZSIsInN0YXJ0VGltZVJlZiIsInJlZiIsInRpbWUiLCJ0Iiwic2V0VGltZW91dCIsImNsZWFyVGltZW91dCIsImN1cnJlbnQiLCJlbGFwc2VkIiwiaHVlIl0sInNvdXJjZXMiOlsiQW5pbWF0ZWRBc3Rlcmlzay50c3giXSwic291cmNlc0NvbnRlbnQiOlsiaW1wb3J0ICogYXMgUmVhY3QgZnJvbSAncmVhY3QnXG5pbXBvcnQgeyB1c2VFZmZlY3QsIHVzZVJlZiwgdXNlU3RhdGUgfSBmcm9tICdyZWFjdCdcbmltcG9ydCB7IFRFQVJEUk9QX0FTVEVSSVNLIH0gZnJvbSAnLi4vLi4vY29uc3RhbnRzL2ZpZ3VyZXMuanMnXG5pbXBvcnQgeyBCb3gsIFRleHQsIHVzZUFuaW1hdGlvbkZyYW1lIH0gZnJvbSAnLi4vLi4vaW5rLmpzJ1xuaW1wb3J0IHsgZ2V0SW5pdGlhbFNldHRpbmdzIH0gZnJvbSAnLi4vLi4vdXRpbHMvc2V0dGluZ3Mvc2V0dGluZ3MuanMnXG5pbXBvcnQgeyBodWVUb1JnYiwgdG9SR0JDb2xvciB9IGZyb20gJy4uL1NwaW5uZXIvdXRpbHMuanMnXG5cbmNvbnN0IFNXRUVQX0RVUkFUSU9OX01TID0gMTUwMFxuY29uc3QgU1dFRVBfQ09VTlQgPSAyXG5jb25zdCBUT1RBTF9BTklNQVRJT05fTVMgPSBTV0VFUF9EVVJBVElPTl9NUyAqIFNXRUVQX0NPVU5UXG5jb25zdCBTRVRUTEVEX0dSRVkgPSB0b1JHQkNvbG9yKHsgcjogMTUzLCBnOiAxNTMsIGI6IDE1MyB9KVxuXG5leHBvcnQgZnVuY3Rpb24gQW5pbWF0ZWRBc3Rlcmlzayh7XG4gIGNoYXIgPSBURUFSRFJPUF9BU1RFUklTSyxcbn06IHtcbiAgY2hhcj86IHN0cmluZ1xufSk6IFJlYWN0LlJlYWN0Tm9kZSB7XG4gIC8vIFJlYWQgcHJlZmVyc1JlZHVjZWRNb3Rpb24gb25jZSBhdCBtb3VudCDigJQgbm8gdXNlU2V0dGluZ3MoKSBzdWJzY3JpcHRpb24sXG4gIC8vIHNpbmNlIHRoYXQgd291bGQgcmUtcmVuZGVyIHdoZW5ldmVyIHNldHRpbmdzIGNoYW5nZS5cbiAgY29uc3QgW3JlZHVjZWRNb3Rpb25dID0gdXNlU3RhdGUoXG4gICAgKCkgPT4gZ2V0SW5pdGlhbFNldHRpbmdzKCkucHJlZmVyc1JlZHVjZWRNb3Rpb24gPz8gZmFsc2UsXG4gIClcbiAgY29uc3QgW2RvbmUsIHNldERvbmVdID0gdXNlU3RhdGUocmVkdWNlZE1vdGlvbilcbiAgLy8gdXNlQW5pbWF0aW9uRnJhbWUncyBjbG9jayBpcyBzaGFyZWQg4oCUIGNhcHR1cmUgb3VyIHN0YXJ0IG9mZnNldCBzbyB0aGVcbiAgLy8gc3dlZXAgYWx3YXlzIGJlZ2lucyBhdCBodWUgMCByZWdhcmRsZXNzIG9mIHdoZW4gd2UgbW91bnQuXG4gIGNvbnN0IHN0YXJ0VGltZVJlZiA9IHVzZVJlZjxudW1iZXIgfCBudWxsPihudWxsKVxuICAvLyBXaXJlIHRoZSByZWYgc28gdXNlQW5pbWF0aW9uRnJhbWUncyB2aWV3cG9ydC1wYXVzZSBraWNrcyBpbjogaWYgdGhlXG4gIC8vIHVzZXIgc3VibWl0cyBhIG1lc3NhZ2UgYmVmb3JlIHRoZSBzd2VlcCBmaW5pc2hlcywgdGhlIGNsb2NrIHN0b3BzXG4gIC8vIGF1dG9tYXRpY2FsbHkgb25jZSB0aGlzIHJvdyBlbnRlcnMgc2Nyb2xsYmFjayAocHJldmVudHMgZmxpY2tlcikuXG4gIGNvbnN0IFtyZWYsIHRpbWVdID0gdXNlQW5pbWF0aW9uRnJhbWUoZG9uZSA/IG51bGwgOiA1MClcblxuICB1c2VFZmZlY3QoKCkgPT4ge1xuICAgIGlmIChkb25lKSByZXR1cm5cbiAgICBjb25zdCB0ID0gc2V0VGltZW91dChzZXREb25lLCBUT1RBTF9BTklNQVRJT05fTVMsIHRydWUpXG4gICAgcmV0dXJuICgpID0+IGNsZWFyVGltZW91dCh0KVxuICB9LCBbZG9uZV0pXG5cbiAgaWYgKGRvbmUpIHtcbiAgICByZXR1cm4gKFxuICAgICAgPEJveCByZWY9e3JlZn0+XG4gICAgICAgIDxUZXh0IGNvbG9yPXtTRVRUTEVEX0dSRVl9PntjaGFyfTwvVGV4dD5cbiAgICAgIDwvQm94PlxuICAgIClcbiAgfVxuXG4gIGlmIChzdGFydFRpbWVSZWYuY3VycmVudCA9PT0gbnVsbCkge1xuICAgIHN0YXJ0VGltZVJlZi5jdXJyZW50ID0gdGltZVxuICB9XG4gIGNvbnN0IGVsYXBzZWQgPSB0aW1lIC0gc3RhcnRUaW1lUmVmLmN1cnJlbnRcbiAgY29uc3QgaHVlID0gKChlbGFwc2VkIC8gU1dFRVBfRFVSQVRJT05fTVMpICogMzYwKSAlIDM2MFxuXG4gIHJldHVybiAoXG4gICAgPEJveCByZWY9e3JlZn0+XG4gICAgICA8VGV4dCBjb2xvcj17dG9SR0JDb2xvcihodWVUb1JnYihodWUpKX0+e2NoYXJ9PC9UZXh0PlxuICAgIDwvQm94PlxuICApXG59XG4iXSwibWFwcGluZ3MiOiJBQUFBLE9BQU8sS0FBS0EsS0FBSyxNQUFNLE9BQU87QUFDOUIsU0FBU0MsU0FBUyxFQUFFQyxNQUFNLEVBQUVDLFFBQVEsUUFBUSxPQUFPO0FBQ25ELFNBQVNDLGlCQUFpQixRQUFRLDRCQUE0QjtBQUM5RCxTQUFTQyxHQUFHLEVBQUVDLElBQUksRUFBRUMsaUJBQWlCLFFBQVEsY0FBYztBQUMzRCxTQUFTQyxrQkFBa0IsUUFBUSxrQ0FBa0M7QUFDckUsU0FBU0MsUUFBUSxFQUFFQyxVQUFVLFFBQVEscUJBQXFCO0FBRTFELE1BQU1DLGlCQUFpQixHQUFHLElBQUk7QUFDOUIsTUFBTUMsV0FBVyxHQUFHLENBQUM7QUFDckIsTUFBTUMsa0JBQWtCLEdBQUdGLGlCQUFpQixHQUFHQyxXQUFXO0FBQzFELE1BQU1FLFlBQVksR0FBR0osVUFBVSxDQUFDO0VBQUVLLENBQUMsRUFBRSxHQUFHO0VBQUVDLENBQUMsRUFBRSxHQUFHO0VBQUVDLENBQUMsRUFBRTtBQUFJLENBQUMsQ0FBQztBQUUzRCxPQUFPLFNBQVNDLGdCQUFnQkEsQ0FBQztFQUMvQkMsSUFBSSxHQUFHZjtBQUdULENBRkMsRUFBRTtFQUNEZSxJQUFJLENBQUMsRUFBRSxNQUFNO0FBQ2YsQ0FBQyxDQUFDLEVBQUVuQixLQUFLLENBQUNvQixTQUFTLENBQUM7RUFDbEI7RUFDQTtFQUNBLE1BQU0sQ0FBQ0MsYUFBYSxDQUFDLEdBQUdsQixRQUFRLENBQzlCLE1BQU1LLGtCQUFrQixDQUFDLENBQUMsQ0FBQ2Msb0JBQW9CLElBQUksS0FDckQsQ0FBQztFQUNELE1BQU0sQ0FBQ0MsSUFBSSxFQUFFQyxPQUFPLENBQUMsR0FBR3JCLFFBQVEsQ0FBQ2tCLGFBQWEsQ0FBQztFQUMvQztFQUNBO0VBQ0EsTUFBTUksWUFBWSxHQUFHdkIsTUFBTSxDQUFDLE1BQU0sR0FBRyxJQUFJLENBQUMsQ0FBQyxJQUFJLENBQUM7RUFDaEQ7RUFDQTtFQUNBO0VBQ0EsTUFBTSxDQUFDd0IsR0FBRyxFQUFFQyxJQUFJLENBQUMsR0FBR3BCLGlCQUFpQixDQUFDZ0IsSUFBSSxHQUFHLElBQUksR0FBRyxFQUFFLENBQUM7RUFFdkR0QixTQUFTLENBQUMsTUFBTTtJQUNkLElBQUlzQixJQUFJLEVBQUU7SUFDVixNQUFNSyxDQUFDLEdBQUdDLFVBQVUsQ0FBQ0wsT0FBTyxFQUFFWCxrQkFBa0IsRUFBRSxJQUFJLENBQUM7SUFDdkQsT0FBTyxNQUFNaUIsWUFBWSxDQUFDRixDQUFDLENBQUM7RUFDOUIsQ0FBQyxFQUFFLENBQUNMLElBQUksQ0FBQyxDQUFDO0VBRVYsSUFBSUEsSUFBSSxFQUFFO0lBQ1IsT0FDRSxDQUFDLEdBQUcsQ0FBQyxHQUFHLENBQUMsQ0FBQ0csR0FBRyxDQUFDO0FBQ3BCLFFBQVEsQ0FBQyxJQUFJLENBQUMsS0FBSyxDQUFDLENBQUNaLFlBQVksQ0FBQyxDQUFDLENBQUNLLElBQUksQ0FBQyxFQUFFLElBQUk7QUFDL0MsTUFBTSxFQUFFLEdBQUcsQ0FBQztFQUVWO0VBRUEsSUFBSU0sWUFBWSxDQUFDTSxPQUFPLEtBQUssSUFBSSxFQUFFO0lBQ2pDTixZQUFZLENBQUNNLE9BQU8sR0FBR0osSUFBSTtFQUM3QjtFQUNBLE1BQU1LLE9BQU8sR0FBR0wsSUFBSSxHQUFHRixZQUFZLENBQUNNLE9BQU87RUFDM0MsTUFBTUUsR0FBRyxHQUFLRCxPQUFPLEdBQUdyQixpQkFBaUIsR0FBSSxHQUFHLEdBQUksR0FBRztFQUV2RCxPQUNFLENBQUMsR0FBRyxDQUFDLEdBQUcsQ0FBQyxDQUFDZSxHQUFHLENBQUM7QUFDbEIsTUFBTSxDQUFDLElBQUksQ0FBQyxLQUFLLENBQUMsQ0FBQ2hCLFVBQVUsQ0FBQ0QsUUFBUSxDQUFDd0IsR0FBRyxDQUFDLENBQUMsQ0FBQyxDQUFDLENBQUNkLElBQUksQ0FBQyxFQUFFLElBQUk7QUFDMUQsSUFBSSxFQUFFLEdBQUcsQ0FBQztBQUVWIiwiaWdub3JlTGlzdCI6W119