Apple Intelligence

An Apple Intelligence style overlay provider

Last updated on

Edit on GitHub

Manual

Install the following dependencies:

npm install react-native-reanimated @shopify/react-native-skia react-native-worklets

Copy and paste the following code into your project. component/organisms/apple-intelligence.tsx

// @ts-checkimport React, { memo, useCallback, useRef, useState } from "react";import { StyleSheet, View, ViewStyle } from "react-native";import {  Canvas,  Fill,  Shader,  ImageShader,  makeImageFromView,  type SkImage,} from "@shopify/react-native-skia";import {  useSharedValue,  withTiming,  Easing,  useAnimatedStyle,} from "react-native-reanimated";import Animated from "react-native-reanimated";import { scheduleOnRN } from "react-native-worklets";import { SiriContext } from "./context";import { SHADER_SOURCE } from "./conf";import { useSiriUniforms } from "./use-siri-uniforms";import type { IAppleIntelligenceProvider, ISiriToggleOptions } from "./types";export const SiriProvider: React.FC<IAppleIntelligenceProvider> &  React.FunctionComponent<IAppleIntelligenceProvider> = memo<  IAppleIntelligenceProvider & React.ComponentProps<typeof SiriProvider>>(  ({    children,    introDuration = 1200,    outroDuration = 600,    wave: defaultWave,    noise: defaultNoise,    glow: defaultGlow,    shimmer: defaultShimmer,    border: defaultBorder,  }: IAppleIntelligenceProvider & React.ComponentProps<typeof SiriProvider>):    | (React.ReactNode & React.JSX.Element & React.ReactElement)    | null => {    const viewRef = useRef<View>(null);    const [snapshot, setSnapshot] = useState<SkImage | null>(null);    const [active, setActive] = useState<boolean>(false);    const [layout, setLayout] = useState<{ width: number; height: number }>({      width: 0,      height: 0,    });    const busyRef = useRef<boolean>(false);    const startTimeRef = useRef<number>(0);    const intervalRef = useRef<ReturnType<typeof setInterval> | null>(null);    const [overlayContent, setOverlayContent] = useState<React.ReactNode>(null);    const overlayOpacity = useSharedValue<number>(0);    const { iTime, intensity, uniforms, applyConfig } = useSiriUniforms({      layout,      defaultWave,      defaultNoise,      defaultGlow,      defaultShimmer,      defaultBorder,    });    const overlayStyle = useAnimatedStyle<      Required<Partial<Pick<ViewStyle, "opacity">>>    >(() => ({      opacity: overlayOpacity.value,    }));    const stopClock = useCallback(() => {      if (intervalRef.current) {        clearInterval(intervalRef.current);        intervalRef.current = null;      }    }, []);    const dismiss = useCallback(() => {      stopClock();      busyRef.current = false;      setActive(false);      setSnapshot(null);      setOverlayContent(null);    }, [stopClock]);    const startClock = useCallback(() => {      startTimeRef.current = Date.now();      intervalRef.current = setInterval(() => {        iTime.value = (Date.now() - startTimeRef.current) / 1000;      }, 16);    }, [iTime]);    const toggle = useCallback(      async (options?: ISiriToggleOptions) => {        if (active) {          busyRef.current = true;          intensity.value = withTiming<number>(            0,            { duration: outroDuration, easing: Easing.out(Easing.quad) },            (finished) => {              if (finished) {                overlayOpacity.value = withTiming(                  0,                  { duration: 200 },                  (done) => {                    if (done) scheduleOnRN(dismiss);                  },                );              }            },          );          return;        }        if (          busyRef.current ||          !viewRef.current ||          layout.width === 0 ||          layout.height === 0        )          return;        busyRef.current = true;        try {          const image = await makeImageFromView(viewRef);          if (!image) {            busyRef.current = false;            return;          }          applyConfig(options);          intensity.value = 0;          iTime.value = 0;          overlayOpacity.value = 1;          setSnapshot(image);          setActive(true);          startClock();          busyRef.current = false;          intensity.value = withTiming(1, {            duration: introDuration,            easing: Easing.bezier(0.25, 0.1, 0.25, 1),          });        } catch (err) {          busyRef.current = false;          console.warn("[SiriProvider]", err);        }      },      [        active,        layout,        introDuration,        outroDuration,        applyConfig,        startClock,        dismiss,        intensity,        overlayOpacity,        iTime,      ],    );    const setOverlay = useCallback((content: React.ReactNode) => {      setOverlayContent(content);    }, []);    return (      <SiriContext.Provider value={{ toggle, isActive: active, setOverlay }}>        <View          ref={viewRef}          style={styles.container}          collapsable={false}          onLayout={(e) => {            const { width, height } = e.nativeEvent.layout;            setLayout({ width, height });          }}        >          {children}        </View>        {active && snapshot && layout.width > 0 && (          <Animated.View            style={[styles.overlay, overlayStyle]}            pointerEvents="none"          >            <Canvas style={{ width: layout.width, height: layout.height }}>              <Fill>                <Shader source={SHADER_SOURCE} uniforms={uniforms}>                  <ImageShader                    image={snapshot}                    fit="cover"                    width={layout.width}                    height={layout.height}                  />                </Shader>              </Fill>            </Canvas>          </Animated.View>        )}        {overlayContent}      </SiriContext.Provider>    );  },);export default memo<  React.FC<IAppleIntelligenceProvider> &    React.FunctionComponent<IAppleIntelligenceProvider> &    React.ComponentProps<typeof SiriProvider>>(SiriProvider);const styles = StyleSheet.create({  container: { flex: 1 },  overlay: { ...StyleSheet.absoluteFillObject, zIndex: 9999 },});

Usage

import React, { useCallback, useEffect } from "react";import {  View,  Text,  StyleSheet,  Pressable,  StatusBar,  Dimensions,} from "react-native";import { useFonts } from "expo-font";import Animated, {  FadeIn,  FadeOut,  FadeInUp,  ZoomIn,  ZoomOut,  useSharedValue,  useAnimatedStyle,  withRepeat,  withSequence,  withTiming,  withDelay,  Easing,  interpolate,} from "react-native-reanimated";import Svg, { Path } from "react-native-svg";import { useSiri } from "@/components/organisms/apple-intelligence/context";const { width } = Dimensions.get("window");const PulseDot = ({ delay = 0, color }: { delay?: number; color: string }) => {  const scale = useSharedValue(1);  useEffect(() => {    scale.value = withDelay(      delay,      withRepeat(        withSequence(          withTiming(1.8, { duration: 600, easing: Easing.out(Easing.quad) }),          withTiming(1, { duration: 600, easing: Easing.in(Easing.quad) }),        ),        -1,        true,      ),    );  }, []);  const style = useAnimatedStyle(() => ({    transform: [{ scale: scale.value }],    opacity: interpolate(scale.value, [1, 1.8], [0.3, 1]),  }));  return (    <Animated.View      style={[        { width: 6, height: 6, borderRadius: 3, backgroundColor: color },        style,      ]}    />  );};const ListeningOverlay = ({ onDismiss }: { onDismiss: () => void }) => (  <Animated.View    style={styles.overlay}    entering={FadeIn.duration(200)}    exiting={FadeOut.duration(180)}  >    <Pressable style={StyleSheet.absoluteFillObject} onPress={onDismiss} />    <Animated.View      style={styles.panel}      entering={ZoomIn.duration(350).easing(Easing.out(Easing.exp))}      exiting={ZoomOut.duration(200)}    >      <Animated.View        style={styles.dotsRow}        entering={FadeIn.delay(120).duration(300)}      >        <PulseDot delay={0} color="#C44AFF" />        <PulseDot delay={100} color="#FF6B9D" />        <PulseDot delay={200} color="#00C9FF" />      </Animated.View>      <Animated.Text        style={styles.listeningLabel}        entering={FadeInUp.delay(80).duration(320)}      >        Listening…      </Animated.Text>      <Animated.Text        style={styles.listeningSubtitle}        entering={FadeInUp.delay(160).duration(320)}      >        What can I help you with?      </Animated.Text>      <Animated.View        style={styles.waveRow}        entering={FadeIn.delay(240).duration(300)}      >        {[0.3, 0.7, 1, 0.7, 0.5, 0.9, 0.4, 0.8, 0.6, 1, 0.5, 0.3].map(          (h, i) => (            <View              key={i}              style={[                styles.waveBar,                {                  height: 4 + h * 28,                  opacity: 0.4 + h * 0.5,                },              ]}            />          ),        )}      </Animated.View>      <Animated.View entering={FadeIn.delay(300).duration(300)}>        <Pressable style={styles.dismissBtn} onPress={onDismiss}>          <Text style={styles.dismissText}>Done</Text>        </Pressable>      </Animated.View>    </Animated.View>  </Animated.View>);export default function Index() {  const [fontLoaded] = useFonts({    SfProRounded: require("@/assets/fonts/sf-pro-rounded.ttf"),    HelveticaNowDisplay: require("@/assets/fonts/HelveticaNowDisplayMedium.ttf"),  });  const { toggle, isActive, setOverlay } = useSiri();  const handleDismiss = useCallback(() => {    setOverlay(null);    toggle();  }, [setOverlay, toggle]);  useEffect(() => {    if (isActive) {      setOverlay(<ListeningOverlay onDismiss={handleDismiss} />);    } else {      setOverlay(null);    }  }, [isActive]);  const handleActivate = useCallback(() => {    toggle({      glow: {        colors: ["#C44AFF", "#FF6B9D", "#00C9FF", "#5856D6", "#C44AFF"],        speed: 0.18,        saturation: 1,        lightness: 0.65,      },      border: { radius: 50, spread: 22, margin: 5 },      wave: { strength: 1, speed: 2.5, origin: [0.5, 0.8] },    });  }, [toggle]);  if (!fontLoaded) return <View style={styles.root} />;  return (    <View style={styles.root}>      <StatusBar barStyle="light-content" />      <View style={styles.center}>        <Text style={[styles.headline, { fontFamily: "HelveticaNowDisplay" }]}>          Hey, what's{"\n"}on your mind?        </Text>        <Text style={[styles.subtitle, { fontFamily: "SfProRounded" }]}>          Tap below and watch the magic happen.        </Text>      </View>      <View style={styles.bottomArea}>        <Pressable style={styles.siriBtn} onPress={handleActivate}>          <View style={styles.siriBtnInner}>            <Text style={[styles.siriBtnText, { fontFamily: "SfProRounded" }]}>              Ask Siri            </Text>          </View>        </Pressable>        <Text style={[styles.hint, { fontFamily: "SfProRounded" }]}>          Tap anywhere on overlay to dismiss        </Text>      </View>    </View>  );}const styles = StyleSheet.create({  root: {    flex: 1,    backgroundColor: "#000",    paddingTop: 60,    paddingBottom: 48,    paddingHorizontal: 28,  },  topBadge: {    flexDirection: "row",    alignItems: "center",    gap: 6,    alignSelf: "center",    backgroundColor: "rgba(255,255,255,0.06)",    borderWidth: 1,    borderColor: "rgba(255,255,255,0.08)",    borderRadius: 20,    paddingHorizontal: 14,    paddingVertical: 7,  },  topBadgeText: {    color: "rgba(255,255,255,0.55)",    fontSize: 12,    fontWeight: "600",    letterSpacing: 0.3,  },  center: {    flex: 1,    alignItems: "center",    justifyContent: "center",  },  glowOrb: {    width: 180,    height: 180,    borderRadius: 90,    backgroundColor: "purple",    borderWidth: 1,    borderColor: "rgba(196,74,255,0.15)",    shadowColor: "#C44AFF",    shadowOffset: { width: 0, height: 0 },    shadowOpacity: 0.5,    shadowRadius: 60,    marginBottom: 40,  },  headline: {    fontSize: 38,    fontWeight: "700",    color: "#fff",    letterSpacing: -1.2,    lineHeight: 46,    textAlign: "center",    marginBottom: 14,  },  subtitle: {    fontSize: 15,    color: "rgba(255,255,255,0.35)",    textAlign: "center",    letterSpacing: 0.1,  },  bottomArea: {    alignItems: "center",    gap: 14,  },  siriBtn: {    width: width - 56,    height: 58,    borderRadius: 29,    backgroundColor: "#fff",    alignItems: "center",    justifyContent: "center",    shadowColor: "#fff",    shadowOffset: { width: 0, height: 0 },    shadowOpacity: 0.15,    shadowRadius: 20,  },  siriBtnInner: {    flexDirection: "row",    alignItems: "center",    gap: 8,  },  siriBtnText: {    color: "#000",    fontSize: 17,    fontWeight: "700",    letterSpacing: -0.3,  },  hint: {    fontSize: 12,    color: "rgba(255,255,255,0.2)",    letterSpacing: 0.2,  },  overlay: {    ...StyleSheet.absoluteFillObject,    zIndex: 10000,    alignItems: "center",    justifyContent: "center",    backgroundColor: "rgba(0,0,0,0.3)",  },  panel: {    width: width - 48,    backgroundColor: "rgba(16,16,16,0.97)",    borderRadius: 32,    borderWidth: 1,    borderColor: "rgba(255,255,255,0.07)",    paddingVertical: 36,    paddingHorizontal: 28,    alignItems: "center",    gap: 0,  },  dotsRow: {    flexDirection: "row",    gap: 7,    marginBottom: 22,  },  listeningLabel: {    fontSize: 28,    fontWeight: "700",    color: "#fff",    letterSpacing: -0.8,    marginBottom: 8,    fontFamily: "HelveticaNowDisplay",  },  listeningSubtitle: {    fontSize: 14,    color: "rgba(255,255,255,0.4)",    marginBottom: 32,    fontFamily: "SfProRounded",    letterSpacing: 0.1,  },  waveRow: {    flexDirection: "row",    alignItems: "center",    gap: 5,    marginBottom: 32,    height: 36,  },  waveBar: {    width: 3,    borderRadius: 2,    backgroundColor: "#C44AFF",  },  dismissBtn: {    paddingHorizontal: 32,    paddingVertical: 12,    borderRadius: 22,    backgroundColor: "rgba(255,255,255,0.07)",    borderWidth: 1,    borderColor: "rgba(255,255,255,0.08)",  },  dismissText: {    color: "rgba(255,255,255,0.6)",    fontSize: 15,    fontWeight: "600",    fontFamily: "SfProRounded",    letterSpacing: 0.2,  },});

Props

ISiriToggleOptions

React Native Reanimated
React Native Skia
React Native Worklets

On this page