react-native-ai-animations 0.9.0 → 0.9.1
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.
- package/README.md +2 -0
- package/lib/module/chat/effort-bar.js +38 -27
- package/lib/module/chat/effort-bar.js.map +1 -1
- package/lib/module/chat/question-card.js +1 -1
- package/lib/module/chat/question-card.js.map +1 -1
- package/lib/module/chat/status-row.js +6 -4
- package/lib/module/chat/status-row.js.map +1 -1
- package/lib/module/chat/thinking-block.js +3 -2
- package/lib/module/chat/thinking-block.js.map +1 -1
- package/lib/module/internal/color.js +24 -9
- package/lib/module/internal/color.js.map +1 -1
- package/lib/module/internal/graphemes.js +38 -0
- package/lib/module/internal/graphemes.js.map +1 -0
- package/lib/module/internal/use-elapsed.js +2 -1
- package/lib/module/internal/use-elapsed.js.map +1 -1
- package/lib/module/internal/use-motion-clock.js +11 -0
- package/lib/module/internal/use-motion-clock.js.map +1 -0
- package/lib/module/skia/dot-orb.js +5 -4
- package/lib/module/skia/dot-orb.js.map +1 -1
- package/lib/module/skia/matrix-orb.js +6 -3
- package/lib/module/skia/matrix-orb.js.map +1 -1
- package/lib/module/skia/media-generating.js +4 -3
- package/lib/module/skia/media-generating.js.map +1 -1
- package/lib/module/skia/voice-wave.js +8 -3
- package/lib/module/skia/voice-wave.js.map +1 -1
- package/lib/module/text/shimmer-text.js +22 -3
- package/lib/module/text/shimmer-text.js.map +1 -1
- package/lib/module/text/spoken-text.js +6 -1
- package/lib/module/text/spoken-text.js.map +1 -1
- package/lib/module/text/streaming-text.js +3 -3
- package/lib/module/text/streaming-text.js.map +1 -1
- package/lib/typescript/src/chat/effort-bar.d.ts.map +1 -1
- package/lib/typescript/src/chat/status-row.d.ts +2 -1
- package/lib/typescript/src/chat/status-row.d.ts.map +1 -1
- package/lib/typescript/src/chat/thinking-block.d.ts.map +1 -1
- package/lib/typescript/src/internal/color.d.ts +4 -0
- package/lib/typescript/src/internal/color.d.ts.map +1 -1
- package/lib/typescript/src/internal/graphemes.d.ts +4 -0
- package/lib/typescript/src/internal/graphemes.d.ts.map +1 -0
- package/lib/typescript/src/internal/use-elapsed.d.ts.map +1 -1
- package/lib/typescript/src/internal/use-motion-clock.d.ts +3 -0
- package/lib/typescript/src/internal/use-motion-clock.d.ts.map +1 -0
- package/lib/typescript/src/skia/dot-orb.d.ts.map +1 -1
- package/lib/typescript/src/skia/matrix-orb.d.ts +3 -1
- package/lib/typescript/src/skia/matrix-orb.d.ts.map +1 -1
- package/lib/typescript/src/skia/media-generating.d.ts.map +1 -1
- package/lib/typescript/src/skia/voice-wave.d.ts +2 -2
- package/lib/typescript/src/skia/voice-wave.d.ts.map +1 -1
- package/lib/typescript/src/text/shimmer-text.d.ts.map +1 -1
- package/lib/typescript/src/text/spoken-text.d.ts.map +1 -1
- package/lib/typescript/src/text/streaming-text.d.ts.map +1 -1
- package/package.json +1 -1
- package/src/chat/effort-bar.tsx +40 -27
- package/src/chat/question-card.tsx +1 -1
- package/src/chat/status-row.tsx +7 -4
- package/src/chat/thinking-block.tsx +3 -2
- package/src/internal/color.ts +30 -15
- package/src/internal/graphemes.ts +40 -0
- package/src/internal/use-elapsed.ts +2 -1
- package/src/internal/use-motion-clock.ts +9 -0
- package/src/skia/dot-orb.tsx +5 -4
- package/src/skia/matrix-orb.tsx +7 -2
- package/src/skia/media-generating.tsx +3 -3
- package/src/skia/voice-wave.tsx +9 -4
- package/src/text/shimmer-text.tsx +25 -3
- package/src/text/spoken-text.tsx +5 -1
- package/src/text/streaming-text.tsx +6 -6
|
@@ -126,6 +126,7 @@ export function ThinkingBlock({
|
|
|
126
126
|
};
|
|
127
127
|
|
|
128
128
|
const heading = title ?? (thinking ? copy.thinking : copy.done(totalSeconds));
|
|
129
|
+
const label = thinking && startedAt !== undefined ? `${heading} ${elapsed}s` : heading;
|
|
129
130
|
|
|
130
131
|
return (
|
|
131
132
|
<View style={style}>
|
|
@@ -142,13 +143,13 @@ export function ThinkingBlock({
|
|
|
142
143
|
<Animated.View key={thinking ? "thinking" : "done"} entering={reduceMotion ? undefined : FadeIn.duration(220)}>
|
|
143
144
|
{thinking && !reduceMotion ? (
|
|
144
145
|
<ShimmerText
|
|
145
|
-
text={
|
|
146
|
+
text={label}
|
|
146
147
|
activeColor={palette.text}
|
|
147
148
|
mutedColor={palette.textTertiary}
|
|
148
149
|
textStyle={textStyle}
|
|
149
150
|
/>
|
|
150
151
|
) : (
|
|
151
|
-
<Text style={[textStyle, { color: palette.textSecondary }]}>{
|
|
152
|
+
<Text style={[textStyle, { color: palette.textSecondary }]}>{label}</Text>
|
|
152
153
|
)}
|
|
153
154
|
</Animated.View>
|
|
154
155
|
{hasBody && (
|
package/src/internal/color.ts
CHANGED
|
@@ -1,18 +1,33 @@
|
|
|
1
|
+
import { processColor } from "react-native";
|
|
2
|
+
|
|
3
|
+
type Rgba = [number, number, number, number];
|
|
4
|
+
|
|
5
|
+
export function parseColor(color: string): Rgba | null {
|
|
6
|
+
const processed = processColor(color);
|
|
7
|
+
if (typeof processed !== "number") return null;
|
|
8
|
+
const argb = processed >>> 0;
|
|
9
|
+
return [(argb >>> 16) & 255, (argb >>> 8) & 255, argb & 255, (argb >>> 24) / 255];
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
function roundAlpha(value: number) {
|
|
13
|
+
return Math.round(value * 1000) / 1000;
|
|
14
|
+
}
|
|
15
|
+
|
|
1
16
|
export function withAlpha(color: string, alpha: number): string {
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
17
|
+
const parsed = parseColor(color);
|
|
18
|
+
if (!parsed) return color;
|
|
19
|
+
const [red, green, blue, source] = parsed;
|
|
20
|
+
return `rgba(${red}, ${green}, ${blue}, ${roundAlpha(source * alpha)})`;
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
export function mixColors(from: string, to: string, t: number): string {
|
|
24
|
+
const a = parseColor(from);
|
|
25
|
+
const b = parseColor(to);
|
|
26
|
+
if (!a || !b) return t < 0.5 ? from : to;
|
|
27
|
+
const [red, green, blue, alpha] = a.map((value, index) => value + (b[index] - value) * t);
|
|
28
|
+
if (alpha >= 1) {
|
|
29
|
+
const hex = (value: number) => Math.round(value).toString(16).padStart(2, "0");
|
|
30
|
+
return `#${hex(red)}${hex(green)}${hex(blue)}`;
|
|
5
31
|
}
|
|
6
|
-
|
|
7
|
-
const full =
|
|
8
|
-
hex.length === 3
|
|
9
|
-
? hex
|
|
10
|
-
.split("")
|
|
11
|
-
.map((char) => char + char)
|
|
12
|
-
.join("")
|
|
13
|
-
: hex.slice(0, 6);
|
|
14
|
-
const r = parseInt(full.slice(0, 2), 16);
|
|
15
|
-
const g = parseInt(full.slice(2, 4), 16);
|
|
16
|
-
const b = parseInt(full.slice(4, 6), 16);
|
|
17
|
-
return `rgba(${r}, ${g}, ${b}, ${alpha})`;
|
|
32
|
+
return `rgba(${Math.round(red)}, ${Math.round(green)}, ${Math.round(blue)}, ${roundAlpha(alpha)})`;
|
|
18
33
|
}
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
const EXTEND = /^[\p{M}\u200C\uFE00-\uFE0F\u{E0020}-\u{E007F}\u{1F3FB}-\u{1F3FF}]$/u;
|
|
2
|
+
const REGIONAL = /^[\u{1F1E6}-\u{1F1FF}]$/u;
|
|
3
|
+
const ZWJ = "\u200D";
|
|
4
|
+
const SHAPED = /[\u0590-\u08FF\u0900-\u0DFF\u0E00-\u0FFF\u1000-\u109F\u1780-\u18AF\uA800-\uABFF\uFB1D-\uFDFF\uFE70-\uFEFF]/;
|
|
5
|
+
const CJK = /^[\u2E80-\u303F\u3040-\u9FFF\uAC00-\uD7AF\uF900-\uFAFF\uFF00-\uFFEF]/;
|
|
6
|
+
|
|
7
|
+
type Segmenter = { segment(text: string): Iterable<{ segment: string }> };
|
|
8
|
+
type SegmenterConstructor = new (locale?: string, options?: { granularity: "grapheme" }) => Segmenter;
|
|
9
|
+
|
|
10
|
+
function nativeSegmenter(): Segmenter | null {
|
|
11
|
+
const constructor = (Intl as unknown as { Segmenter?: SegmenterConstructor }).Segmenter;
|
|
12
|
+
return constructor ? new constructor(undefined, { granularity: "grapheme" }) : null;
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
export function splitGraphemes(text: string): string[] {
|
|
16
|
+
const segmenter = typeof Intl === "undefined" ? null : nativeSegmenter();
|
|
17
|
+
if (segmenter) return Array.from(segmenter.segment(text), (part) => part.segment);
|
|
18
|
+
const clusters: string[] = [];
|
|
19
|
+
let joinNext = false;
|
|
20
|
+
for (const point of text) {
|
|
21
|
+
const last = clusters.length - 1;
|
|
22
|
+
const previous = clusters[last];
|
|
23
|
+
const pairsFlag = REGIONAL.test(point) && previous !== undefined && REGIONAL.test(previous);
|
|
24
|
+
if (previous !== undefined && (joinNext || point === ZWJ || EXTEND.test(point) || pairsFlag)) {
|
|
25
|
+
clusters[last] = previous + point;
|
|
26
|
+
} else {
|
|
27
|
+
clusters.push(point);
|
|
28
|
+
}
|
|
29
|
+
joinNext = point === ZWJ;
|
|
30
|
+
}
|
|
31
|
+
return clusters;
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
export function needsShaping(text: string) {
|
|
35
|
+
return SHAPED.test(text);
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
export function isCjk(cluster: string) {
|
|
39
|
+
return CJK.test(cluster);
|
|
40
|
+
}
|
|
@@ -4,8 +4,9 @@ export function useElapsed(startedAt: number | undefined, running: boolean, inte
|
|
|
4
4
|
const [now, setNow] = useState(() => Date.now());
|
|
5
5
|
|
|
6
6
|
useEffect(() => {
|
|
7
|
-
if (
|
|
7
|
+
if (startedAt === undefined) return;
|
|
8
8
|
setNow(Date.now());
|
|
9
|
+
if (!running) return;
|
|
9
10
|
const id = setInterval(() => setNow(Date.now()), interval);
|
|
10
11
|
return () => clearInterval(id);
|
|
11
12
|
}, [interval, running, startedAt]);
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
import { type SharedValue, useFrameCallback, useSharedValue } from "react-native-reanimated";
|
|
2
|
+
|
|
3
|
+
export function useMotionClock(running: boolean, initial = 0): SharedValue<number> {
|
|
4
|
+
const clock = useSharedValue(initial);
|
|
5
|
+
useFrameCallback((info) => {
|
|
6
|
+
clock.value += info.timeSincePreviousFrame ?? 0;
|
|
7
|
+
}, running);
|
|
8
|
+
return clock;
|
|
9
|
+
}
|
package/src/skia/dot-orb.tsx
CHANGED
|
@@ -1,7 +1,8 @@
|
|
|
1
|
-
import { Canvas, Path, Skia, type SkPath
|
|
1
|
+
import { Canvas, Path, Skia, type SkPath } from "@shopify/react-native-skia";
|
|
2
2
|
import { useEffect, useMemo } from "react";
|
|
3
3
|
import { type StyleProp, type ViewStyle } from "react-native";
|
|
4
4
|
import { Easing, useDerivedValue, useReducedMotion, useSharedValue, withDelay, withRepeat, withSequence, withTiming } from "react-native-reanimated";
|
|
5
|
+
import { useMotionClock } from "../internal/use-motion-clock";
|
|
5
6
|
import { useMotionPalette } from "../theme/provider";
|
|
6
7
|
|
|
7
8
|
export type DotOrbVariant = "orbit" | "scan" | "constellation" | "helix";
|
|
@@ -96,9 +97,9 @@ export function DotOrb({
|
|
|
96
97
|
const reduceMotion = useReducedMotion();
|
|
97
98
|
const ink = color ?? palette.text;
|
|
98
99
|
const highlight = accent ?? palette.accent;
|
|
99
|
-
const clock = useClock();
|
|
100
|
-
const layout = useSharedValue({ width: size ?? 0, height: size ?? 0 });
|
|
101
100
|
const still = reduceMotion || paused;
|
|
101
|
+
const clock = useMotionClock(!still);
|
|
102
|
+
const layout = useSharedValue({ width: size ?? 0, height: size ?? 0 });
|
|
102
103
|
const spread = useSharedValue(scatter === true ? 1 : 0);
|
|
103
104
|
|
|
104
105
|
useEffect(() => {
|
|
@@ -116,7 +117,7 @@ export function DotOrb({
|
|
|
116
117
|
const edges = useMemo(() => (variant === "constellation" ? buildEdges(sphere, 0.6) : []), [sphere, variant]);
|
|
117
118
|
|
|
118
119
|
const frame = useDerivedValue<Frame>(() => {
|
|
119
|
-
const t = (
|
|
120
|
+
const t = (reduceMotion ? STILL_TIME : clock.value) * 0.001 * speed;
|
|
120
121
|
const width = layout.value.width;
|
|
121
122
|
const height = layout.value.height;
|
|
122
123
|
const cx = width / 2;
|
package/src/skia/matrix-orb.tsx
CHANGED
|
@@ -7,6 +7,8 @@ import {
|
|
|
7
7
|
useReducedMotion,
|
|
8
8
|
useSharedValue,
|
|
9
9
|
} from "react-native-reanimated";
|
|
10
|
+
import { type MotionPalette } from "../theme/palette";
|
|
11
|
+
import { useMotionPalette } from "../theme/provider";
|
|
10
12
|
|
|
11
13
|
export type MatrixOrbState = "idle" | "listening" | "thinking";
|
|
12
14
|
|
|
@@ -24,7 +26,6 @@ const MAX_DELTA = 0.05;
|
|
|
24
26
|
const MIN_DOTS = 3;
|
|
25
27
|
const DEFAULT_DOTS = 11;
|
|
26
28
|
const DEFAULT_SIZE = 240;
|
|
27
|
-
const DEFAULT_COLOR = "#FFFFFF";
|
|
28
29
|
|
|
29
30
|
const ORB_SPAN = 0.74;
|
|
30
31
|
const ORB_DOT = 0.6;
|
|
@@ -195,6 +196,7 @@ export type MatrixOrbProps = {
|
|
|
195
196
|
color?: string;
|
|
196
197
|
stateColors?: Partial<Record<MatrixOrbState, string>>;
|
|
197
198
|
dots?: number;
|
|
199
|
+
colors?: Partial<MotionPalette>;
|
|
198
200
|
};
|
|
199
201
|
|
|
200
202
|
export function MatrixOrb({
|
|
@@ -202,10 +204,13 @@ export function MatrixOrb({
|
|
|
202
204
|
level,
|
|
203
205
|
size = DEFAULT_SIZE,
|
|
204
206
|
cover = false,
|
|
205
|
-
color
|
|
207
|
+
color: colorProp,
|
|
206
208
|
stateColors,
|
|
207
209
|
dots = DEFAULT_DOTS,
|
|
210
|
+
colors,
|
|
208
211
|
}: MatrixOrbProps) {
|
|
212
|
+
const palette = useMotionPalette(colors);
|
|
213
|
+
const color = colorProp ?? palette.text;
|
|
209
214
|
const reduceMotion = useReducedMotion();
|
|
210
215
|
|
|
211
216
|
const grid = Math.max(MIN_DOTS, Math.round(dots));
|
|
@@ -7,7 +7,6 @@ import {
|
|
|
7
7
|
LinearGradient,
|
|
8
8
|
Paint,
|
|
9
9
|
Rect,
|
|
10
|
-
useClock,
|
|
11
10
|
vec,
|
|
12
11
|
} from "@shopify/react-native-skia";
|
|
13
12
|
import { useEffect, useState, type ReactNode } from "react";
|
|
@@ -36,6 +35,7 @@ import Animated, {
|
|
|
36
35
|
} from "react-native-reanimated";
|
|
37
36
|
import { withAlpha } from "../internal/color";
|
|
38
37
|
import { formatElapsed, useElapsed } from "../internal/use-elapsed";
|
|
38
|
+
import { useMotionClock } from "../internal/use-motion-clock";
|
|
39
39
|
import { ShimmerText } from "../text/shimmer-text";
|
|
40
40
|
import { moodstoneSwatches, type MotionPalette } from "../theme/palette";
|
|
41
41
|
import { useMotionPalette } from "../theme/provider";
|
|
@@ -138,7 +138,7 @@ export function MediaGenerating({
|
|
|
138
138
|
const seconds = useElapsed(startedAt, true);
|
|
139
139
|
const copy = { ...DEFAULT_LABELS, ...labels };
|
|
140
140
|
const title = status ? copy[status] : (label ?? copy.generating);
|
|
141
|
-
const headline = chrome === "header" && label && status ? label : title;
|
|
141
|
+
const headline = chrome === "header" && label && status ? (status === "queued" ? `${label} \u00B7 ${copy.queued}` : label) : title;
|
|
142
142
|
const percent = progress === undefined ? undefined : Math.round(Math.min(1, Math.max(0, progress)) * 100);
|
|
143
143
|
const chip = withAlpha(palette.background, 0.72);
|
|
144
144
|
const showSweep = sweep ?? variant === "blobs";
|
|
@@ -241,7 +241,7 @@ type BlobFieldProps = { colors: string[]; grain: boolean; sweep: boolean; sweepC
|
|
|
241
241
|
|
|
242
242
|
function BlobField({ colors, grain, sweep, sweepColor, still }: BlobFieldProps) {
|
|
243
243
|
const size = useSharedValue({ width: 0, height: 0 });
|
|
244
|
-
const clock =
|
|
244
|
+
const clock = useMotionClock(!still);
|
|
245
245
|
const time = useDerivedValue(() => (still ? 0 : clock.value), [still]);
|
|
246
246
|
const canvasWidth = useDerivedValue(() => size.value.width);
|
|
247
247
|
const canvasHeight = useDerivedValue(() => size.value.height);
|
package/src/skia/voice-wave.tsx
CHANGED
|
@@ -1,7 +1,10 @@
|
|
|
1
|
-
import { Canvas, LinearGradient, Path, Skia,
|
|
1
|
+
import { Canvas, LinearGradient, Path, Skia, vec } from "@shopify/react-native-skia";
|
|
2
2
|
import { useState } from "react";
|
|
3
3
|
import { View, type LayoutChangeEvent, type StyleProp, type ViewStyle } from "react-native";
|
|
4
4
|
import { type SharedValue, useDerivedValue, useFrameCallback, useReducedMotion, useSharedValue } from "react-native-reanimated";
|
|
5
|
+
import { useMotionClock } from "../internal/use-motion-clock";
|
|
6
|
+
import { moodstoneSwatches } from "../theme/palette";
|
|
7
|
+
import { useMotionPalette } from "../theme/provider";
|
|
5
8
|
|
|
6
9
|
const DEFAULT_HEIGHT = 32;
|
|
7
10
|
const SAMPLE_STEP = 2;
|
|
@@ -29,7 +32,7 @@ export type VoiceWaveVariant = "lines" | "bars" | "dots" | "pulse";
|
|
|
29
32
|
|
|
30
33
|
export type VoiceWaveProps = {
|
|
31
34
|
level: SharedValue<number>;
|
|
32
|
-
colors
|
|
35
|
+
colors?: [string, string, string];
|
|
33
36
|
variant?: VoiceWaveVariant;
|
|
34
37
|
height?: number;
|
|
35
38
|
style?: StyleProp<ViewStyle>;
|
|
@@ -168,10 +171,12 @@ function Pulse({ width, height, level, clock, colors }: FieldProps) {
|
|
|
168
171
|
);
|
|
169
172
|
}
|
|
170
173
|
|
|
171
|
-
export function VoiceWave({ level, colors, variant = "lines", height = DEFAULT_HEIGHT, style }: VoiceWaveProps) {
|
|
174
|
+
export function VoiceWave({ level, colors: colorsProp, variant = "lines", height = DEFAULT_HEIGHT, style }: VoiceWaveProps) {
|
|
175
|
+
const palette = useMotionPalette();
|
|
176
|
+
const colors = colorsProp ?? [palette.accent, moodstoneSwatches.sky, moodstoneSwatches.mint];
|
|
172
177
|
const [width, setWidth] = useState(0);
|
|
173
178
|
const reduceMotion = useReducedMotion();
|
|
174
|
-
const ticking =
|
|
179
|
+
const ticking = useMotionClock(!reduceMotion);
|
|
175
180
|
const clock = useDerivedValue(() => (reduceMotion ? 1200 : ticking.value), [reduceMotion]);
|
|
176
181
|
|
|
177
182
|
const handleLayout = (event: LayoutChangeEvent) => setWidth(event.nativeEvent.layout.width);
|
|
@@ -14,6 +14,7 @@ import Animated, {
|
|
|
14
14
|
withSequence,
|
|
15
15
|
withTiming,
|
|
16
16
|
} from "react-native-reanimated";
|
|
17
|
+
import { isCjk, needsShaping, splitGraphemes } from "../internal/graphemes";
|
|
17
18
|
|
|
18
19
|
const DEFAULT_DURATION = 1400;
|
|
19
20
|
const DEFAULT_HIGHLIGHT_WIDTH = 0.22;
|
|
@@ -50,20 +51,26 @@ export function ShimmerText({
|
|
|
50
51
|
}: ShimmerTextProps) {
|
|
51
52
|
const reduceMotion = useReducedMotion();
|
|
52
53
|
const progress = useSharedValue(0);
|
|
54
|
+
const shaped = useMemo(() => needsShaping(text), [text]);
|
|
53
55
|
const { words, total } = useMemo(() => {
|
|
54
|
-
const characters =
|
|
56
|
+
const characters = shaped ? [] : splitGraphemes(text);
|
|
55
57
|
const groups: Glyph[][] = [];
|
|
56
58
|
let current: Glyph[] = [];
|
|
57
59
|
characters.forEach((char, index) => {
|
|
60
|
+
const breakAfter = char === " " || isCjk(char);
|
|
61
|
+
if (isCjk(char) && current.length) {
|
|
62
|
+
groups.push(current);
|
|
63
|
+
current = [];
|
|
64
|
+
}
|
|
58
65
|
current.push({ char, index });
|
|
59
|
-
if (
|
|
66
|
+
if (breakAfter) {
|
|
60
67
|
groups.push(current);
|
|
61
68
|
current = [];
|
|
62
69
|
}
|
|
63
70
|
});
|
|
64
71
|
if (current.length) groups.push(current);
|
|
65
72
|
return { words: groups, total: characters.length };
|
|
66
|
-
}, [text]);
|
|
73
|
+
}, [shaped, text]);
|
|
67
74
|
|
|
68
75
|
useEffect(() => {
|
|
69
76
|
if (reduceMotion) return;
|
|
@@ -80,6 +87,21 @@ export function ShimmerText({
|
|
|
80
87
|
return <Text style={[textStyle, { color: activeColor }]}>{text}</Text>;
|
|
81
88
|
}
|
|
82
89
|
|
|
90
|
+
if (shaped) {
|
|
91
|
+
return (
|
|
92
|
+
<ShimmerChar
|
|
93
|
+
char={text}
|
|
94
|
+
index={1}
|
|
95
|
+
total={3}
|
|
96
|
+
progress={progress}
|
|
97
|
+
activeColor={activeColor}
|
|
98
|
+
mutedColor={mutedColor}
|
|
99
|
+
textStyle={textStyle}
|
|
100
|
+
highlightWidth={Math.max(highlightWidth, 0.5)}
|
|
101
|
+
/>
|
|
102
|
+
);
|
|
103
|
+
}
|
|
104
|
+
|
|
83
105
|
return (
|
|
84
106
|
<View
|
|
85
107
|
accessible
|
package/src/text/spoken-text.tsx
CHANGED
|
@@ -40,7 +40,11 @@ export function SpokenText({
|
|
|
40
40
|
if (reduceMotion) {
|
|
41
41
|
return (
|
|
42
42
|
<Text accessibilityLabel={text} style={[textStyle, { color: active, textAlign: align }, style as StyleProp<TextStyle>]}>
|
|
43
|
-
{
|
|
43
|
+
{words.map((word, index) => (
|
|
44
|
+
<Text key={index} style={{ color: index < current ? done : index === current ? active : upcoming }}>
|
|
45
|
+
{word}
|
|
46
|
+
</Text>
|
|
47
|
+
))}
|
|
44
48
|
</Text>
|
|
45
49
|
);
|
|
46
50
|
}
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { isValidElement, memo, useEffect, useMemo, useState, type ReactNode } from "react";
|
|
2
|
-
import { StyleSheet, Text, View, type StyleProp, type TextStyle, type ViewStyle } from "react-native";
|
|
2
|
+
import { StyleSheet, Text, View, type ColorValue, type StyleProp, type TextStyle, type ViewStyle } from "react-native";
|
|
3
3
|
import Animated, {
|
|
4
4
|
cancelAnimation,
|
|
5
5
|
useAnimatedStyle,
|
|
@@ -9,7 +9,6 @@ import Animated, {
|
|
|
9
9
|
withSequence,
|
|
10
10
|
withTiming,
|
|
11
11
|
} from "react-native-reanimated";
|
|
12
|
-
import { withAlpha } from "../internal/color";
|
|
13
12
|
import { type MotionPalette } from "../theme/palette";
|
|
14
13
|
import { useMotionPalette } from "../theme/provider";
|
|
15
14
|
|
|
@@ -108,7 +107,8 @@ export function StreamingText({
|
|
|
108
107
|
const [reveal, setReveal] = useState<Reveal>(() => (streaming && animate ? settledReveal("") : settledReveal(text)));
|
|
109
108
|
const [now, setNow] = useState(() => Date.now());
|
|
110
109
|
const fading = reveal.chunks.length > 0;
|
|
111
|
-
const
|
|
110
|
+
const flatStyle = StyleSheet.flatten(textStyle);
|
|
111
|
+
const color = flatStyle?.color ?? palette.text;
|
|
112
112
|
|
|
113
113
|
useEffect(() => {
|
|
114
114
|
if (!streaming || !animate) {
|
|
@@ -164,7 +164,7 @@ export function StreamingText({
|
|
|
164
164
|
const value = text.slice(Math.max(piece.start, range.start), Math.min(piece.end, range.end));
|
|
165
165
|
if (piece.alpha >= 1) return value;
|
|
166
166
|
return (
|
|
167
|
-
<Text key={piece.start} style={{
|
|
167
|
+
<Text key={piece.start} style={{ opacity: piece.alpha }}>
|
|
168
168
|
{value}
|
|
169
169
|
</Text>
|
|
170
170
|
);
|
|
@@ -180,7 +180,7 @@ export function StreamingText({
|
|
|
180
180
|
type PlainParagraphProps = {
|
|
181
181
|
value: string;
|
|
182
182
|
textStyle: StyleProp<TextStyle>;
|
|
183
|
-
color:
|
|
183
|
+
color: ColorValue;
|
|
184
184
|
selectable: boolean;
|
|
185
185
|
};
|
|
186
186
|
|
|
@@ -192,7 +192,7 @@ const PlainParagraph = memo(function PlainParagraph({ value, textStyle, color, s
|
|
|
192
192
|
);
|
|
193
193
|
});
|
|
194
194
|
|
|
195
|
-
function Caret({ color, blink, textStyle }: { color:
|
|
195
|
+
function Caret({ color, blink, textStyle }: { color: ColorValue; blink: boolean; textStyle: StyleProp<TextStyle> }) {
|
|
196
196
|
const opacity = useSharedValue(1);
|
|
197
197
|
const flat = StyleSheet.flatten(textStyle);
|
|
198
198
|
const height = Math.round((flat?.fontSize ?? 16) * 1.05);
|