// TransformControlPanel: absolute flip + 90-degree rotation, themed and
// controlled. Every change emits the full TransformInput (the transform verb is
// absolute). Toggles read the `active` theme slot for their on state.
import type { StyleProp, ViewStyle } from 'react-native';
import { Pressable, StyleSheet, Text, View } from 'react-native';
import type { TransformInput } from '../../kaleidoscope/types';
import { flipTestId, rotateTestId, TRANSFORM_TESTID_PREFIX } from '../../lib/test-id';
import { useThemeSlot } from '../theme/provider';
import { ControlSection } from './control-section';
const ROTATIONS = [0, 90, 180, 270] as const;
export type TransformControlPanelProps = {
readonly flip?: { readonly x?: boolean; readonly y?: boolean };
readonly rotate?: number;
readonly onChange: (transform: TransformInput) => void;
readonly disabled?: boolean;
/** Root for this instance's test ids; override when a screen mounts two. */
readonly testIDPrefix?: string;
};
function FlipToggle({
label,
icon,
on,
disabled,
onPress,
testID,
accessibilityLabel,
}: {
readonly label: string;
readonly icon: string;
readonly on: boolean;
readonly disabled: boolean;
readonly onPress: () => void;
readonly testID: string;
readonly accessibilityLabel: string;
}) {
const { style: activeStyle } = useThemeSlot('active');
return (
),
disabled && styles.disabled,
]}
>
{icon}
{label}
);
}
export function TransformControlPanel({
flip,
rotate = 0,
onChange,
disabled = false,
testIDPrefix = TRANSFORM_TESTID_PREFIX,
}: TransformControlPanelProps) {
const x = flip?.x ?? false;
const y = flip?.y ?? false;
const { style: activeStyle } = useThemeSlot('active');
return (
onChange({ flip: { x: !x, y }, rotate })}
testID={flipTestId(testIDPrefix, 'x')}
accessibilityLabel="Flip horizontal"
/>
onChange({ flip: { x, y: !y }, rotate })}
testID={flipTestId(testIDPrefix, 'y')}
accessibilityLabel="Flip vertical"
/>
{ROTATIONS.map((deg) => {
const on = rotate === deg;
return (
onChange({ flip: { x, y }, rotate: deg })}
style={[
styles.rot,
on && styles.on,
on && (activeStyle as StyleProp),
disabled && styles.disabled,
]}
>
{deg}°
);
})}
);
}
const styles = StyleSheet.create({
flipRow: { flexDirection: 'row', gap: 8 },
rotRow: { flexDirection: 'row', flexWrap: 'wrap', gap: 8 },
flip: {
flex: 1,
paddingVertical: 12,
backgroundColor: '#2a2a2a',
borderRadius: 6,
alignItems: 'center',
gap: 2,
},
rot: {
flexGrow: 1,
minWidth: 56,
paddingVertical: 10,
backgroundColor: '#2a2a2a',
borderRadius: 6,
alignItems: 'center',
},
on: { backgroundColor: '#4a8f3f' },
disabled: { opacity: 0.5 },
flipIcon: { color: '#fff', fontSize: 22, lineHeight: 26 },
flipLabel: { color: '#fff', fontWeight: '500', fontSize: 13 },
rotText: { color: '#fff', fontWeight: '600', fontSize: 13 },
});