diff --git a/example/__tests__/ios/__image_snapshots__/ios/absolute-positioning-basic.png b/example/__tests__/ios/__image_snapshots__/ios/absolute-positioning-basic.png new file mode 100644 index 0000000..b779010 Binary files /dev/null and b/example/__tests__/ios/__image_snapshots__/ios/absolute-positioning-basic.png differ diff --git a/example/__tests__/ios/__image_snapshots__/ios/absolute-positioning-corners.png b/example/__tests__/ios/__image_snapshots__/ios/absolute-positioning-corners.png new file mode 100644 index 0000000..b0e2944 Binary files /dev/null and b/example/__tests__/ios/__image_snapshots__/ios/absolute-positioning-corners.png differ diff --git a/example/__tests__/ios/__image_snapshots__/ios/badge-overlay.png b/example/__tests__/ios/__image_snapshots__/ios/badge-overlay.png new file mode 100644 index 0000000..bb459d3 Binary files /dev/null and b/example/__tests__/ios/__image_snapshots__/ios/badge-overlay.png differ diff --git a/example/__tests__/ios/__image_snapshots__/ios/relative-positioning-basic.png b/example/__tests__/ios/__image_snapshots__/ios/relative-positioning-basic.png new file mode 100644 index 0000000..193deaa Binary files /dev/null and b/example/__tests__/ios/__image_snapshots__/ios/relative-positioning-basic.png differ diff --git a/example/__tests__/ios/__image_snapshots__/ios/relative-positioning-negative.png b/example/__tests__/ios/__image_snapshots__/ios/relative-positioning-negative.png new file mode 100644 index 0000000..9900a49 Binary files /dev/null and b/example/__tests__/ios/__image_snapshots__/ios/relative-positioning-negative.png differ diff --git a/example/__tests__/ios/__image_snapshots__/ios/static-positioning.png b/example/__tests__/ios/__image_snapshots__/ios/static-positioning.png new file mode 100644 index 0000000..536cf65 Binary files /dev/null and b/example/__tests__/ios/__image_snapshots__/ios/static-positioning.png differ diff --git a/example/__tests__/ios/__image_snapshots__/ios/z-index-layering.png b/example/__tests__/ios/__image_snapshots__/ios/z-index-layering.png new file mode 100644 index 0000000..4078552 Binary files /dev/null and b/example/__tests__/ios/__image_snapshots__/ios/z-index-layering.png differ diff --git a/example/__tests__/ios/positioning.harness.tsx b/example/__tests__/ios/positioning.harness.tsx new file mode 100644 index 0000000..7adc62a --- /dev/null +++ b/example/__tests__/ios/positioning.harness.tsx @@ -0,0 +1,46 @@ +import { screen } from '@react-native-harness/ui' +import { ComponentType } from 'react' +import { View } from 'react-native' +import { describe, expect, render, test } from 'react-native-harness' + +import { + AbsolutePositioningBasicExample, + AbsolutePositioningCornersExample, + BadgeOverlayExample, + RelativePositioningBasicExample, + RelativePositioningNegativeExample, + StaticPositioningExample, + ZIndexLayeringExample, +} from '../../screens/testing-grounds/positioning/PositioningExamples' + +const snapshotTest = (name: string, Component: ComponentType<{ testID?: string }>) => { + return test(`should match snapshot for ${name}`, async () => { + await render( + + + + ) + + const previewElement = await screen.findByTestId('preview') + const screenshot = await screen.screenshot(previewElement) + await expect(screenshot).toMatchImageSnapshot({ + name: name.toLowerCase().replace(/\s+/g, '-'), + }) + }) +} + +describe('Positioning snapshots', () => { + snapshotTest('Static Positioning', StaticPositioningExample) + + snapshotTest('Relative Positioning Basic', RelativePositioningBasicExample) + + snapshotTest('Relative Positioning Negative', RelativePositioningNegativeExample) + + snapshotTest('Absolute Positioning Basic', AbsolutePositioningBasicExample) + + snapshotTest('Absolute Positioning Corners', AbsolutePositioningCornersExample) + + snapshotTest('Z-Index Layering', ZIndexLayeringExample) + + snapshotTest('Badge Overlay', BadgeOverlayExample) +}) diff --git a/example/app/testing-grounds/positioning.tsx b/example/app/testing-grounds/positioning.tsx new file mode 100644 index 0000000..6d9e54e --- /dev/null +++ b/example/app/testing-grounds/positioning.tsx @@ -0,0 +1,5 @@ +import PositioningScreen from '~/screens/testing-grounds/positioning/PositioningScreen' + +export default function PositioningIndex() { + return +} diff --git a/example/screens/testing-grounds/TestingGroundsScreen.tsx b/example/screens/testing-grounds/TestingGroundsScreen.tsx index e2f5484..1b25908 100644 --- a/example/screens/testing-grounds/TestingGroundsScreen.tsx +++ b/example/screens/testing-grounds/TestingGroundsScreen.tsx @@ -20,6 +20,13 @@ const TESTING_GROUNDS_SECTIONS = [ 'Explore Voltra styling properties including padding, margins, colors, borders, shadows, and typography.', route: '/testing-grounds/styling', }, + { + id: 'positioning', + title: 'Positioning', + description: + 'Learn about static, relative, and absolute positioning modes. See how left, top, and zIndex properties work with visual examples.', + route: '/testing-grounds/positioning', + }, { id: 'components', title: 'Components', diff --git a/example/screens/testing-grounds/positioning/PositioningExamples.tsx b/example/screens/testing-grounds/positioning/PositioningExamples.tsx new file mode 100644 index 0000000..e2ddaea --- /dev/null +++ b/example/screens/testing-grounds/positioning/PositioningExamples.tsx @@ -0,0 +1,361 @@ +import React from 'react' +import { Voltra } from 'voltra' +import { VoltraView } from 'voltra/client' + +export type PositioningExampleProps = { + testID?: string +} + +export function StaticPositioningExample({ testID }: PositioningExampleProps) { + return ( + + + {/* This box has left/top but NO position - should be centered and ignore left/top */} + + Static + (Centered) + + + + ) +} + +export function RelativePositioningBasicExample({ testID }: PositioningExampleProps) { + return ( + + + {/* Reference box showing natural position (top-left) */} + + Natural + + + {/* Relatively positioned box - offset from natural position */} + + Relative + +20, +10 + + + + ) +} + +export function RelativePositioningNegativeExample({ testID }: PositioningExampleProps) { + return ( + + + {/* Reference box at center */} + + Natural + + + {/* Relatively positioned with negative offset */} + + Relative + -15, -15 + + + + ) +} + +export function AbsolutePositioningBasicExample({ testID }: PositioningExampleProps) { + return ( + + + {/* Crosshair marker at (50, 50) to show center point */} + + + {/* Absolutely positioned box - center should be at (50, 50) */} + + Absolute + @50, 50 + + + + ) +} + +export function AbsolutePositioningCornersExample({ testID }: PositioningExampleProps) { + return ( + + + + {/* Top-left box */} + + 30,30 + + + {/* Bottom-right box */} + + 200,170 + + + {/* Center box */} + + 115,100 + + + {/* Center marker */} + + + {/* Bottom-right corner marker */} + + + {/* Top-left corner marker */} + + + + + ) +} + +export function ZIndexLayeringExample({ testID }: PositioningExampleProps) { + return ( + + + {/* Bottom layer (zIndex: 1) */} + + z: 1 + + + {/* Middle layer (zIndex: 2) */} + + z: 2 + + + {/* Top layer (zIndex: 3) */} + + z: 3 + + + + ) +} + +export function BadgeOverlayExample({ testID }: PositioningExampleProps) { + return ( + + + {/* Avatar with badge overlay */} + + + + {/* Notification Badge - Absolutely positioned on avatar */} + + 3 + + + + {/* Info */} + + John Doe + Software Engineer + + + + ) +} diff --git a/example/screens/testing-grounds/positioning/PositioningScreen.tsx b/example/screens/testing-grounds/positioning/PositioningScreen.tsx new file mode 100644 index 0000000..c62fd2e --- /dev/null +++ b/example/screens/testing-grounds/positioning/PositioningScreen.tsx @@ -0,0 +1,136 @@ +import { Link } from 'expo-router' +import React from 'react' +import { FlatList, StyleSheet, Text, View } from 'react-native' + +import { Button } from '~/components/Button' +import { Card } from '~/components/Card' + +import { + AbsolutePositioningBasicExample, + AbsolutePositioningCornersExample, + BadgeOverlayExample, + RelativePositioningBasicExample, + RelativePositioningNegativeExample, + StaticPositioningExample, + ZIndexLayeringExample, +} from './PositioningExamples' + +const POSITIONING_DATA = [ + { + id: 'static-default', + title: 'Static Positioning (Default)', + description: 'When position is not set or set to "static", left and top are ignored. Box should stay centered.', + Component: StaticPositioningExample, + }, + { + id: 'relative-basic', + title: 'Relative Positioning - Basic', + description: + 'position: "relative" offsets the box from its natural position. left: 20, top: 10 moves it right and down.', + Component: RelativePositioningBasicExample, + }, + { + id: 'relative-negative', + title: 'Relative Positioning - Negative Offset', + description: 'Negative values move the box left (negative left) and up (negative top) from its natural position.', + Component: RelativePositioningNegativeExample, + }, + { + id: 'absolute-basic', + title: 'Absolute Positioning - Center-Based', + description: + 'position: "absolute" places the CENTER of the box at the coordinates. left: 50, top: 50 means center at (50, 50).', + Component: AbsolutePositioningBasicExample, + }, + { + id: 'absolute-corners', + title: 'Absolute Positioning - Four Corners', + description: 'Demonstrating absolute positioning at different coordinates. Red dots mark the center points.', + Component: AbsolutePositioningCornersExample, + }, + { + id: 'zindex-layering', + title: 'Z-Index Layering', + description: 'Using zIndex with positioning to control stacking order.', + Component: ZIndexLayeringExample, + }, + { + id: 'practical-overlay', + title: 'Practical Example - Badge Overlay', + description: 'Using absolute positioning to create a notification badge on a profile card.', + Component: BadgeOverlayExample, + }, +] + +export default function PositioningScreen() { + const renderHeader = () => ( + <> + Positioning Examples + + Explore Voltra's positioning modes: static (default), relative (offset from natural position), and absolute + (center-based coordinates). Red dots mark reference points in absolute positioning examples. + + + ) + + const renderItem = ({ item }: { item: (typeof POSITIONING_DATA)[0] }) => { + const { Component } = item + return ( + + {item.title} + {item.description} + + + ) + } + + const renderFooter = () => ( + + +