Skip to content

feat: Wire CSSPlatformTransition with iOS platform implementation#9325

Open
MatiPl01 wants to merge 7 commits into@matipl01/css-platform-transition-abstractionfrom
@matipl01/ios-css-platform-transitions
Open

feat: Wire CSSPlatformTransition with iOS platform implementation#9325
MatiPl01 wants to merge 7 commits into@matipl01/css-platform-transition-abstractionfrom
@matipl01/ios-css-platform-transitions

Conversation

@MatiPl01
Copy link
Copy Markdown
Member

@MatiPl01 MatiPl01 commented Apr 30, 2026

Adds platform logic for CSS transitions that uses Core Animation to animate supported properties.

Example

Simulator.Screen.Recording.-.iPhone.17.Pro.-.2026-04-30.at.19.34.56.mp4
Source code
import React, { useEffect, useState } from 'react';
import { Pressable, StyleSheet, Text, View } from 'react-native';
import Animated from 'react-native-reanimated';

type Case = {
  label: string;
  delay: number;
  duration: number;
};

// Cases chosen to exercise edge combos:
// - negative delay (animation should start mid-progress)
// - zero delay
// - delay shorter than duration (typical)
// - delay equal to duration
// - delay longer than duration (wait, then quick fade)
// - very short duration with long delay
const CASES: Case[] = [
  {
    label: 'delay -200, dur 600 (neg, starts mid)',
    delay: -200,
    duration: 600,
  },
  { label: 'delay 0, dur 500 (immediate)', delay: 0, duration: 500 },
  { label: 'delay 200, dur 500 (delay < dur)', delay: 200, duration: 500 },
  { label: 'delay 500, dur 500 (delay == dur)', delay: 500, duration: 500 },
  { label: 'delay 800, dur 200 (delay > dur)', delay: 800, duration: 200 },
  { label: 'delay 400, dur 80 (snap after wait)', delay: 400, duration: 80 },
];

const RE_RENDER_INTERVAL_MS = 100;
const BOX_COLORS = ['#4ade80', '#60a5fa', '#f472b6', '#fbbf24'];

export default function EmptyExample() {
  const [visible, setVisible] = useState(true);
  const [reRenderOn, setReRenderOn] = useState(false);
  const [tick, setTick] = useState(0);

  useEffect(() => {
    if (!reRenderOn) return;
    const id = setInterval(() => setTick((t) => t + 1), RE_RENDER_INTERVAL_MS);
    return () => clearInterval(id);
  }, [reRenderOn]);

  return (
    <View style={styles.container}>
      <View style={styles.rows}>
        {CASES.map(({ label, delay, duration }, i) => (
          <View key={i} style={styles.row}>
            <Animated.View
              style={[
                styles.box,
                {
                  backgroundColor: BOX_COLORS[(tick + i) % BOX_COLORS.length],
                  opacity: visible ? 1 : 0,
                  transitionProperty: 'opacity',
                  transitionDuration: duration,
                  transitionDelay: delay,
                  transitionTimingFunction: 'ease-out',
                },
              ]}
            />
            <Text style={styles.label}>{label}</Text>
          </View>
        ))}
      </View>

      <View style={styles.buttons}>
        <Pressable
          style={[styles.button, styles.primaryButton]}
          onPress={() => setVisible((v) => !v)}>
          <Text style={styles.primaryButtonText}>
            {visible ? 'Hide' : 'Show'} (toggle opacity)
          </Text>
        </Pressable>

        <Pressable
          style={[
            styles.button,
            reRenderOn ? styles.dangerButton : styles.secondaryButton,
          ]}
          onPress={() => setReRenderOn((on) => !on)}>
          <Text style={styles.secondaryButtonText}>
            {reRenderOn
              ? `Stop re-renders (tick=${tick})`
              : 'Start re-renders (every 100ms)'}
          </Text>
        </Pressable>
      </View>
    </View>
  );
}

const styles = StyleSheet.create({
  container: {
    flex: 1,
    paddingTop: 80,
    paddingHorizontal: 24,
    justifyContent: 'space-between',
    paddingBottom: 40,
    backgroundColor: '#0f172a',
  },
  rows: {
    gap: 14,
  },
  row: {
    flexDirection: 'row',
    alignItems: 'center',
    gap: 14,
  },
  box: {
    width: 56,
    height: 56,
    borderRadius: 8,
  },
  label: {
    color: '#e5e7eb',
    fontSize: 13,
    flexShrink: 1,
  },
  buttons: {
    gap: 12,
  },
  button: {
    paddingHorizontal: 20,
    paddingVertical: 14,
    borderRadius: 10,
    alignItems: 'center',
  },
  primaryButton: {
    backgroundColor: '#4ade80',
  },
  primaryButtonText: {
    color: '#000',
    fontWeight: '700',
    fontSize: 16,
  },
  secondaryButton: {
    backgroundColor: '#374151',
  },
  dangerButton: {
    backgroundColor: '#f87171',
  },
  secondaryButtonText: {
    color: '#fff',
    fontWeight: '600',
    fontSize: 14,
  },
});

@MatiPl01 MatiPl01 self-assigned this Apr 30, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant