About this animation
A project list with arcing flights into pinned cards, with configurable projects, section labels, accent color, and font.
Why this one
A jump between sections can hide where a project went. This version preserves its icon and name throughout the journey, then changes its proportions to fit a compact horizontal shelf. Unpinning returns it to its original order. The landing pin makes the destination explicit.
How it works
- Section
onLayoutmeasurements locate the shelf and list inside the component, rather than using screen dimensions. - Each project's flight has one UI-thread progress value. Position, size, arc height, and rotation derive from that value. Retargeting captures the current geometry before starting another transition.
- React stores discrete membership, names, and menu state. Shared values handle flight, pressure, lift, compacting, and drag without React updates per frame.
- Tap and long-press use Gesture Handler composition. Horizontal dragging projects momentum into a reorder destination; menu arrows offer the same action by tap.
- The list closes the vacated slot, the shelf expands, and the Lucide pin lands with a restrained spring.
- Reduced motion replaces travel and pin motion with a 200 ms fade. Demo timers live outside the component and stop on touch.
Install
npx expo install expo-image react-native-gesture-handler react-native-reanimated react-native-workletsUse a GestureHandlerRootView at the application root. Icons: Lucide (ISC), copied from the Native Animation kit and rendered with Expo Image. No Skia initialization is required. The demo additionally uses expo-linear-gradient and the catalog's loaded fonts; these are not component dependencies.
Usage
import { PinnedSectionList } from './pinned-section-list';
<PinnedSectionList
projects={[
{ id: 'plan', name: 'Project plan', color: '#6366F1', icon: 'folder', members: ['AK'] },
]}
/>const projectsRef = useRef<PinnedSectionListRef>(null);
<PinnedSectionList
ref={projectsRef}
projects={projects}
accentColor="#6366F1"
colorScheme="auto"
fontFamily="YourLoadedFont"
flightDuration={560}
onPinnedChange={(ids) => savePinnedOrder(ids)}
onRename={(id, name) => saveProjectName(id, name)}
onArchive={(id) => archiveProject(id)}
/>
// Invoke from an event handler:
projectsRef.current?.toggle('plan');Props
| Prop | Type | Default | Description |
|---|---|---|---|
| projects | Project[] | Required | Stable unique IDs, names, colors, optional kit icons, initials, and update labels. Original array order is the return order. |
| accentColor | string | #6366F1 | Hex accent used for borders and pins. |
| fontFamily | string | System | A font already loaded by the host app. |
| pinnedTitle | string | Pinned | Shelf heading. |
| listTitle | string | All projects | List heading. |
| emptyLabel | string | Long-press to pin a project | Empty shelf instruction. |
| colorScheme | 'light' | 'dark' | 'auto' | light | Surface and text treatment. |
| flightDuration | number | 560 | Pin/unpin travel duration in milliseconds. |
| onPinnedChange | (ids: string[]) => void | — | Membership and reorder callback. |
| onRename | (id: string, name: string) => void | — | Committed rename callback. |
| onArchive | (id: string) => void | — | Archive callback. |
| onInteraction | () => void | — | Stops an external autoplay controller. |
| style | StyleProp<ViewStyle> | — | Root container style. |
| testID | string | — | Root test identifier. |
Project fields: id, name, color, optional icon, members, and updated (default Updated 2h ago). Two compact cards fit on the shelf; arrows expose additional pins. Tap any card for unpin, rename, archive, and reorder controls.
Ref methods: toggle(id), open(id), and reset(). Reset restores archived projects and clears pins, preserving names. State is internal; callbacks let the host persist changes.
Compatibility
Targets Expo SDK 58, React Native 0.88.0, Reanimated 4.7.1, Worklets 0.13.0, and Gesture Handler 3.3. Expo Go compatible. No native directories or app configuration changes are needed.
Accessibility
Interactive cards and actions have button roles and labels. Tap offers an alternative to long-press and horizontal drag. Actions have at least 44 pt hit areas; full rows are 64 pt tall. Pinning, returning, reordering, renaming, and archiving announce their outcome and update a polite live region. Reduced motion removes arcs, rotation, lift, and pin bounce, and fades membership changes over 200 ms. Content remains scrollable on smaller screens. The demo reserves the upper-right corner for the catalog close control.




