react-native-ai-animations 0.8.1 → 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 +3 -1
- 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 +151 -55
- 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 +2 -2
- 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 +133 -57
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"media-generating.d.ts","sourceRoot":"","sources":["../../../../src/skia/media-generating.tsx"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"media-generating.d.ts","sourceRoot":"","sources":["../../../../src/skia/media-generating.tsx"],"names":[],"mappings":"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"}
|
|
@@ -3,12 +3,12 @@ import { type SharedValue } from "react-native-reanimated";
|
|
|
3
3
|
export type VoiceWaveVariant = "lines" | "bars" | "dots" | "pulse";
|
|
4
4
|
export type VoiceWaveProps = {
|
|
5
5
|
level: SharedValue<number>;
|
|
6
|
-
colors
|
|
6
|
+
colors?: [string, string, string];
|
|
7
7
|
variant?: VoiceWaveVariant;
|
|
8
8
|
height?: number;
|
|
9
9
|
style?: StyleProp<ViewStyle>;
|
|
10
10
|
};
|
|
11
|
-
export declare function VoiceWave({ level, colors, variant, height, style }: VoiceWaveProps): import("react").JSX.Element;
|
|
11
|
+
export declare function VoiceWave({ level, colors: colorsProp, variant, height, style }: VoiceWaveProps): import("react").JSX.Element;
|
|
12
12
|
export type VoiceWaveStyleProps = Omit<VoiceWaveProps, "variant">;
|
|
13
13
|
export declare function VoiceWaveLines(props: VoiceWaveStyleProps): import("react").JSX.Element;
|
|
14
14
|
export declare function VoiceWaveBars(props: VoiceWaveStyleProps): import("react").JSX.Element;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"voice-wave.d.ts","sourceRoot":"","sources":["../../../../src/skia/voice-wave.tsx"],"names":[],"mappings":"AAEA,OAAO,EAAgC,KAAK,SAAS,EAAE,KAAK,SAAS,EAAE,MAAM,cAAc,CAAC;AAC5F,OAAO,EAAE,KAAK,WAAW,EAAuE,MAAM,yBAAyB,CAAC;
|
|
1
|
+
{"version":3,"file":"voice-wave.d.ts","sourceRoot":"","sources":["../../../../src/skia/voice-wave.tsx"],"names":[],"mappings":"AAEA,OAAO,EAAgC,KAAK,SAAS,EAAE,KAAK,SAAS,EAAE,MAAM,cAAc,CAAC;AAC5F,OAAO,EAAE,KAAK,WAAW,EAAuE,MAAM,yBAAyB,CAAC;AA2BhI,MAAM,MAAM,gBAAgB,GAAG,OAAO,GAAG,MAAM,GAAG,MAAM,GAAG,OAAO,CAAC;AAEnE,MAAM,MAAM,cAAc,GAAG;IAC3B,KAAK,EAAE,WAAW,CAAC,MAAM,CAAC,CAAC;IAC3B,MAAM,CAAC,EAAE,CAAC,MAAM,EAAE,MAAM,EAAE,MAAM,CAAC,CAAC;IAClC,OAAO,CAAC,EAAE,gBAAgB,CAAC;IAC3B,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,KAAK,CAAC,EAAE,SAAS,CAAC,SAAS,CAAC,CAAC;CAC9B,CAAC;AAuIF,wBAAgB,SAAS,CAAC,EAAE,KAAK,EAAE,MAAM,EAAE,UAAU,EAAE,OAAiB,EAAE,MAAuB,EAAE,KAAK,EAAE,EAAE,cAAc,+BA4BzH;AAED,MAAM,MAAM,mBAAmB,GAAG,IAAI,CAAC,cAAc,EAAE,SAAS,CAAC,CAAC;AAElE,wBAAgB,cAAc,CAAC,KAAK,EAAE,mBAAmB,+BAExD;AAED,wBAAgB,aAAa,CAAC,KAAK,EAAE,mBAAmB,+BAEvD;AAED,wBAAgB,aAAa,CAAC,KAAK,EAAE,mBAAmB,+BAEvD;AAED,wBAAgB,cAAc,CAAC,KAAK,EAAE,mBAAmB,+BAExD"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"shimmer-text.d.ts","sourceRoot":"","sources":["../../../../src/text/shimmer-text.tsx"],"names":[],"mappings":"AACA,OAAO,EAAc,KAAK,SAAS,EAAE,KAAK,SAAS,EAAE,MAAM,cAAc,CAAC;
|
|
1
|
+
{"version":3,"file":"shimmer-text.d.ts","sourceRoot":"","sources":["../../../../src/text/shimmer-text.tsx"],"names":[],"mappings":"AACA,OAAO,EAAc,KAAK,SAAS,EAAE,KAAK,SAAS,EAAE,MAAM,cAAc,CAAC;AA2B1E,MAAM,MAAM,gBAAgB,GAAG;IAC7B,IAAI,EAAE,MAAM,CAAC;IACb,WAAW,EAAE,MAAM,CAAC;IACpB,UAAU,EAAE,MAAM,CAAC;IACnB,SAAS,CAAC,EAAE,SAAS,CAAC,SAAS,CAAC,CAAC;IACjC,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,cAAc,CAAC,EAAE,MAAM,CAAC;IACxB,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,SAAS,CAAC,EAAE,KAAK,GAAG,KAAK,CAAC;CAC3B,CAAC;AAIF,wBAAgB,WAAW,CAAC,EAC1B,IAAI,EACJ,WAAW,EACX,UAAU,EACV,SAA8B,EAC9B,QAA2B,EAC3B,cAAwC,EACxC,KAAS,EACT,SAAiB,GAClB,EAAE,gBAAgB,+BAgFlB"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"spoken-text.d.ts","sourceRoot":"","sources":["../../../../src/text/spoken-text.tsx"],"names":[],"mappings":"AACA,OAAO,EAAc,KAAK,SAAS,EAAE,KAAK,SAAS,EAAE,KAAK,SAAS,EAAE,MAAM,cAAc,CAAC;AAE1F,OAAO,EAAE,KAAK,aAAa,EAAE,MAAM,qBAAkB,CAAC;AAKtD,MAAM,MAAM,eAAe,GAAG;IAC5B,IAAI,EAAE,MAAM,CAAC;IACb,MAAM,EAAE,MAAM,CAAC;IACf,SAAS,CAAC,EAAE,SAAS,CAAC,SAAS,CAAC,CAAC;IACjC,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,aAAa,CAAC,EAAE,MAAM,CAAC;IACvB,KAAK,CAAC,EAAE,MAAM,GAAG,QAAQ,CAAC;IAC1B,MAAM,CAAC,EAAE,OAAO,CAAC,aAAa,CAAC,CAAC;IAChC,KAAK,CAAC,EAAE,SAAS,CAAC,SAAS,CAAC,CAAC;CAC9B,CAAC;AAEF,wBAAgB,UAAU,CAAC,EACzB,IAAI,EACJ,MAAM,EACN,SAA8B,EAC9B,WAAW,EACX,WAAW,EACX,aAAa,EACb,KAAgB,EAChB,MAAM,EACN,KAAK,GACN,EAAE,eAAe,+
|
|
1
|
+
{"version":3,"file":"spoken-text.d.ts","sourceRoot":"","sources":["../../../../src/text/spoken-text.tsx"],"names":[],"mappings":"AACA,OAAO,EAAc,KAAK,SAAS,EAAE,KAAK,SAAS,EAAE,KAAK,SAAS,EAAE,MAAM,cAAc,CAAC;AAE1F,OAAO,EAAE,KAAK,aAAa,EAAE,MAAM,qBAAkB,CAAC;AAKtD,MAAM,MAAM,eAAe,GAAG;IAC5B,IAAI,EAAE,MAAM,CAAC;IACb,MAAM,EAAE,MAAM,CAAC;IACf,SAAS,CAAC,EAAE,SAAS,CAAC,SAAS,CAAC,CAAC;IACjC,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,aAAa,CAAC,EAAE,MAAM,CAAC;IACvB,KAAK,CAAC,EAAE,MAAM,GAAG,QAAQ,CAAC;IAC1B,MAAM,CAAC,EAAE,OAAO,CAAC,aAAa,CAAC,CAAC;IAChC,KAAK,CAAC,EAAE,SAAS,CAAC,SAAS,CAAC,CAAC;CAC9B,CAAC;AAEF,wBAAgB,UAAU,CAAC,EACzB,IAAI,EACJ,MAAM,EACN,SAA8B,EAC9B,WAAW,EACX,WAAW,EACX,aAAa,EACb,KAAgB,EAChB,MAAM,EACN,KAAK,GACN,EAAE,eAAe,+BAyCjB"}
|
|
@@ -7,10 +7,10 @@ export type StreamingTextProps = {
|
|
|
7
7
|
textStyle?: StyleProp<TextStyle>;
|
|
8
8
|
paragraphGap?: number;
|
|
9
9
|
fadeDuration?: number;
|
|
10
|
-
rise?: number;
|
|
11
10
|
caret?: boolean | ReactNode;
|
|
11
|
+
selectable?: boolean;
|
|
12
12
|
colors?: Partial<MotionPalette>;
|
|
13
13
|
style?: StyleProp<ViewStyle>;
|
|
14
14
|
};
|
|
15
|
-
export declare function StreamingText({ text, streaming, textStyle, paragraphGap, fadeDuration,
|
|
15
|
+
export declare function StreamingText({ text, streaming, textStyle, paragraphGap, fadeDuration, caret, selectable, colors, style, }: StreamingTextProps): import("react").JSX.Element;
|
|
16
16
|
//# sourceMappingURL=streaming-text.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"streaming-text.d.ts","sourceRoot":"","sources":["../../../../src/text/streaming-text.tsx"],"names":[],"mappings":"AAAA,OAAO,
|
|
1
|
+
{"version":3,"file":"streaming-text.d.ts","sourceRoot":"","sources":["../../../../src/text/streaming-text.tsx"],"names":[],"mappings":"AAAA,OAAO,EAAsD,KAAK,SAAS,EAAE,MAAM,OAAO,CAAC;AAC3F,OAAO,EAA2C,KAAK,SAAS,EAAE,KAAK,SAAS,EAAE,KAAK,SAAS,EAAE,MAAM,cAAc,CAAC;AAUvH,OAAO,EAAE,KAAK,aAAa,EAAE,MAAM,qBAAkB,CAAC;AAQtD,MAAM,MAAM,kBAAkB,GAAG;IAC/B,IAAI,EAAE,MAAM,CAAC;IACb,SAAS,EAAE,OAAO,CAAC;IACnB,SAAS,CAAC,EAAE,SAAS,CAAC,SAAS,CAAC,CAAC;IACjC,YAAY,CAAC,EAAE,MAAM,CAAC;IACtB,YAAY,CAAC,EAAE,MAAM,CAAC;IACtB,KAAK,CAAC,EAAE,OAAO,GAAG,SAAS,CAAC;IAC5B,UAAU,CAAC,EAAE,OAAO,CAAC;IACrB,MAAM,CAAC,EAAE,OAAO,CAAC,aAAa,CAAC,CAAC;IAChC,KAAK,CAAC,EAAE,SAAS,CAAC,SAAS,CAAC,CAAC;CAC9B,CAAC;AA+DF,wBAAgB,aAAa,CAAC,EAC5B,IAAI,EACJ,SAAS,EACT,SAA8B,EAC9B,YAAiB,EACjB,YAA2B,EAC3B,KAAY,EACZ,UAAkB,EAClB,MAAM,EACN,KAAK,GACN,EAAE,kBAAkB,+BA2EpB"}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "react-native-ai-animations",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.9.1",
|
|
4
4
|
"description": "Animated React Native and Expo components for AI chat apps: streaming text, shimmer loading text, thinking and status indicators, voice orbs and waveforms, image and video generation loaders, question cards and reasoning effort bars. Built on Reanimated and Skia.",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"author": "Ahmet Kar",
|
package/src/chat/effort-bar.tsx
CHANGED
|
@@ -20,7 +20,7 @@ import Animated, {
|
|
|
20
20
|
useSharedValue,
|
|
21
21
|
withSpring,
|
|
22
22
|
} from "react-native-reanimated";
|
|
23
|
-
import { withAlpha } from "../internal/color";
|
|
23
|
+
import { mixColors, withAlpha } from "../internal/color";
|
|
24
24
|
import { type MotionPalette } from "../theme/palette";
|
|
25
25
|
import { useMotionPalette } from "../theme/provider";
|
|
26
26
|
|
|
@@ -36,6 +36,7 @@ const SLIDER_TAB = 28;
|
|
|
36
36
|
const SLIDER_TAB_RADIUS = 10;
|
|
37
37
|
const SLIDER_SHOULDER = 8;
|
|
38
38
|
const COMPACT_SLIDER_KNOB = 10;
|
|
39
|
+
const DRAG_SLOP = 4;
|
|
39
40
|
|
|
40
41
|
export type EffortBarVariant = "bars" | "track" | "segmented" | "slider";
|
|
41
42
|
|
|
@@ -66,16 +67,6 @@ function normalize(level: EffortLevel): { key?: string; label: string; descripti
|
|
|
66
67
|
return typeof level === "string" ? { label: level } : level;
|
|
67
68
|
}
|
|
68
69
|
|
|
69
|
-
function mix(a: string, b: string, t: number) {
|
|
70
|
-
const parse = (hex: string) => {
|
|
71
|
-
const clean = hex.replace("#", "");
|
|
72
|
-
return [0, 2, 4].map((offset) => parseInt(clean.slice(offset, offset + 2), 16));
|
|
73
|
-
};
|
|
74
|
-
const from = parse(a);
|
|
75
|
-
const to = parse(b);
|
|
76
|
-
const channel = (index: number) => Math.round(from[index] + (to[index] - from[index]) * t).toString(16).padStart(2, "0");
|
|
77
|
-
return `#${channel(0)}${channel(1)}${channel(2)}`;
|
|
78
|
-
}
|
|
79
70
|
|
|
80
71
|
function spread(stops: string[], count: number) {
|
|
81
72
|
if (count <= 1) return [stops[stops.length - 1]];
|
|
@@ -86,7 +77,7 @@ function spread(stops: string[], count: number) {
|
|
|
86
77
|
const high = Math.min(stops.length - 1, low + 1);
|
|
87
78
|
const a = stops[low];
|
|
88
79
|
const b = stops[high];
|
|
89
|
-
return
|
|
80
|
+
return mixColors(a, b, position - low);
|
|
90
81
|
});
|
|
91
82
|
}
|
|
92
83
|
|
|
@@ -116,7 +107,7 @@ export function EffortBar({
|
|
|
116
107
|
const reduceMotion = useReducedMotion();
|
|
117
108
|
const items = useMemo(() => levels.map(normalize), [levels]);
|
|
118
109
|
const count = Math.max(1, items.length);
|
|
119
|
-
const index = Math.min(count - 1, Math.max(0, value));
|
|
110
|
+
const index = Math.min(count - 1, Math.max(0, Number.isFinite(value) ? Math.round(value) : 0));
|
|
120
111
|
const current = items[index];
|
|
121
112
|
const compact = size === "compact";
|
|
122
113
|
const stops = fillColors && fillColors.length > 0 ? fillColors : undefined;
|
|
@@ -132,6 +123,8 @@ export function EffortBar({
|
|
|
132
123
|
const lastIndex = useRef(index);
|
|
133
124
|
const indexRef = useRef(index);
|
|
134
125
|
const dragging = useRef(false);
|
|
126
|
+
const moved = useRef(false);
|
|
127
|
+
const startX = useRef(0);
|
|
135
128
|
const interactive = !!onChange && !disabled;
|
|
136
129
|
indexRef.current = index;
|
|
137
130
|
|
|
@@ -160,14 +153,15 @@ export function EffortBar({
|
|
|
160
153
|
onChange?.(next, { index: next, label: item.label, value: item.value, budget: item.budget });
|
|
161
154
|
};
|
|
162
155
|
|
|
163
|
-
const pick = (x: number,
|
|
156
|
+
const pick = (x: number, mode: "start" | "move" | "tap") => {
|
|
164
157
|
if (!interactive || width === 0) return;
|
|
165
158
|
let next: number;
|
|
166
159
|
if (variant === "slider") {
|
|
167
160
|
const position = Math.min(1, Math.max(0, (x - knobInset - knobSize / 2) / Math.max(1, width - knobInset * 2 - knobSize)));
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
161
|
+
if (mode !== "tap") {
|
|
162
|
+
dragging.current = true;
|
|
163
|
+
knob.value = reduceMotion || mode === "move" ? position : withSpring(position, GRAB_SPRING);
|
|
164
|
+
}
|
|
171
165
|
next = Math.round(position * (count - 1));
|
|
172
166
|
} else {
|
|
173
167
|
next = Math.min(count - 1, Math.max(0, Math.floor((x / width) * count)));
|
|
@@ -176,15 +170,34 @@ export function EffortBar({
|
|
|
176
170
|
lastIndex.current = next;
|
|
177
171
|
select(next);
|
|
178
172
|
};
|
|
179
|
-
const pickRef = useRef(pick);
|
|
180
|
-
pickRef.current = pick;
|
|
181
173
|
|
|
182
|
-
const
|
|
174
|
+
const grant = (x: number) => {
|
|
175
|
+
startX.current = x;
|
|
176
|
+
moved.current = false;
|
|
177
|
+
lastIndex.current = indexRef.current;
|
|
178
|
+
if (variant === "slider" && !reduceMotion) pressed.value = withSpring(1, GRAB_SPRING);
|
|
179
|
+
};
|
|
180
|
+
const grantRef = useRef(grant);
|
|
181
|
+
grantRef.current = grant;
|
|
182
|
+
|
|
183
|
+
const move = (x: number, dx: number) => {
|
|
184
|
+
if (!moved.current && Math.abs(dx) < DRAG_SLOP) return;
|
|
185
|
+
const first = !moved.current;
|
|
186
|
+
moved.current = true;
|
|
187
|
+
pick(x, first ? "start" : "move");
|
|
188
|
+
};
|
|
189
|
+
const moveRef = useRef(move);
|
|
190
|
+
moveRef.current = move;
|
|
191
|
+
|
|
192
|
+
const release = (commit: boolean) => {
|
|
193
|
+
const tap = commit && !moved.current;
|
|
194
|
+
moved.current = false;
|
|
195
|
+
if (tap) pick(startX.current, "tap");
|
|
196
|
+
if (variant === "slider") pressed.value = reduceMotion ? 0 : withSpring(0, LEVEL_SPRING);
|
|
183
197
|
if (!dragging.current) return;
|
|
184
198
|
dragging.current = false;
|
|
185
199
|
const target = count > 1 ? indexRef.current / (count - 1) : 0;
|
|
186
200
|
knob.value = reduceMotion ? target : withSpring(target, LEVEL_SPRING);
|
|
187
|
-
pressed.value = reduceMotion ? 0 : withSpring(0, LEVEL_SPRING);
|
|
188
201
|
};
|
|
189
202
|
const releaseRef = useRef(release);
|
|
190
203
|
releaseRef.current = release;
|
|
@@ -197,12 +210,12 @@ export function EffortBar({
|
|
|
197
210
|
() =>
|
|
198
211
|
PanResponder.create({
|
|
199
212
|
onStartShouldSetPanResponder: () => true,
|
|
200
|
-
onMoveShouldSetPanResponder: (_, gesture) => Math.abs(gesture.dx) >
|
|
201
|
-
onPanResponderTerminationRequest: () =>
|
|
202
|
-
onPanResponderGrant: (event) =>
|
|
203
|
-
onPanResponderMove: (event) =>
|
|
204
|
-
onPanResponderRelease: () => releaseRef.current(),
|
|
205
|
-
onPanResponderTerminate: () => releaseRef.current(),
|
|
213
|
+
onMoveShouldSetPanResponder: (_, gesture) => Math.abs(gesture.dx) > DRAG_SLOP,
|
|
214
|
+
onPanResponderTerminationRequest: () => !moved.current,
|
|
215
|
+
onPanResponderGrant: (event) => grantRef.current(event.nativeEvent.locationX),
|
|
216
|
+
onPanResponderMove: (event, gesture) => moveRef.current(event.nativeEvent.locationX, gesture.dx),
|
|
217
|
+
onPanResponderRelease: () => releaseRef.current(true),
|
|
218
|
+
onPanResponderTerminate: () => releaseRef.current(false),
|
|
206
219
|
}),
|
|
207
220
|
[],
|
|
208
221
|
);
|
|
@@ -333,7 +333,7 @@ export const QuestionCard = forwardRef<QuestionCardHandle, QuestionCardProps>(fu
|
|
|
333
333
|
const padding: ViewStyle = sheet
|
|
334
334
|
? { paddingHorizontal: 20, paddingTop: 12, paddingBottom: 16 }
|
|
335
335
|
: { paddingHorizontal: 16, paddingTop: 12, paddingBottom: 12 };
|
|
336
|
-
const showFooter = kind === "multiple"
|
|
336
|
+
const showFooter = draft.selected.length > 0 && (kind === "multiple" || (last ? !autoSubmit : !autoAdvance));
|
|
337
337
|
|
|
338
338
|
return (
|
|
339
339
|
<Animated.View style={[container, shellStyle, style]}>
|
package/src/chat/status-row.tsx
CHANGED
|
@@ -28,6 +28,7 @@ export type StatusRowProps = {
|
|
|
28
28
|
iconSize?: number;
|
|
29
29
|
active?: boolean;
|
|
30
30
|
startedAt?: number;
|
|
31
|
+
seconds?: number;
|
|
31
32
|
showTimer?: boolean;
|
|
32
33
|
formatTimer?: (seconds: number) => string;
|
|
33
34
|
textStyle?: StyleProp<TextStyle>;
|
|
@@ -43,6 +44,7 @@ export function StatusRow({
|
|
|
43
44
|
iconSize = 18,
|
|
44
45
|
active = true,
|
|
45
46
|
startedAt,
|
|
47
|
+
seconds,
|
|
46
48
|
showTimer = true,
|
|
47
49
|
formatTimer = formatElapsed,
|
|
48
50
|
textStyle = DEFAULT_TEXT_STYLE,
|
|
@@ -52,12 +54,13 @@ export function StatusRow({
|
|
|
52
54
|
}: StatusRowProps) {
|
|
53
55
|
const palette = useMotionPalette(colors);
|
|
54
56
|
const reduceMotion = useReducedMotion();
|
|
55
|
-
const
|
|
57
|
+
const elapsed = useElapsed(startedAt, active);
|
|
58
|
+
const shownSeconds = seconds ?? elapsed;
|
|
56
59
|
|
|
57
60
|
return (
|
|
58
61
|
<View
|
|
59
62
|
accessible
|
|
60
|
-
accessibilityRole="progressbar"
|
|
63
|
+
accessibilityRole={active ? "progressbar" : "text"}
|
|
61
64
|
accessibilityLabel={label}
|
|
62
65
|
accessibilityState={{ busy: active }}
|
|
63
66
|
style={[{ flexDirection: "row", alignItems: "center", gap: 8 }, style]}
|
|
@@ -83,9 +86,9 @@ export function StatusRow({
|
|
|
83
86
|
)}
|
|
84
87
|
</Animated.View>
|
|
85
88
|
</View>
|
|
86
|
-
{showTimer && startedAt !== undefined && (
|
|
89
|
+
{showTimer && (startedAt !== undefined || seconds !== undefined) && (
|
|
87
90
|
<Text style={[textStyle, { color: palette.textTertiary, fontVariant: ["tabular-nums"] }, timerStyle]}>
|
|
88
|
-
{formatTimer(
|
|
91
|
+
{formatTimer(shownSeconds)}
|
|
89
92
|
</Text>
|
|
90
93
|
)}
|
|
91
94
|
</View>
|
|
@@ -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
|
}
|