A list whose items glide between a pinned and an unpinned group.
Pinned
Drafts
Projects
All
Inbox
Archive
Installation
Usage
import * as React from 'react';
import {
PinnedList,
PinnedListItem,
PinnedListItems,
PinnedListLabel,
PinnedListPinned,
PinnedListTrigger,
PinnedListUnpinned,
} from '@animate-ui/react/primitives/pinned-list';
export function Folders() {
const [items, setItems] = React.useState(ITEMS);
const toggle = (id: string) =>
setItems((prev) =>
prev.map((i) => (i.id === id ? { ...i, pinned: !i.pinned } : i)),
);
const pinned = items.filter((i) => i.pinned);
const unpinned = items.filter((i) => !i.pinned);
return (
<PinnedList onPinnedChange={toggle}>
<PinnedListPinned>
<PinnedListLabel hide={pinned.length === 0}>Pinned</PinnedListLabel>
<PinnedListItems>
{pinned.map((item) => (
<PinnedListItem key={item.id} id={item.id} customTrigger>
{item.name}
<PinnedListTrigger aria-label={`Unpin ${item.name}`}>
…
</PinnedListTrigger>
</PinnedListItem>
))}
</PinnedListItems>
</PinnedListPinned>
<PinnedListUnpinned>
<PinnedListLabel hide={unpinned.length === 0}>All</PinnedListLabel>
<PinnedListItems>
{unpinned.map((item) => (
<PinnedListItem key={item.id} id={item.id} customTrigger>
{item.name}
<PinnedListTrigger aria-label={`Pin ${item.name}`}>
…
</PinnedListTrigger>
</PinnedListItem>
))}
</PinnedListItems>
</PinnedListUnpinned>
</PinnedList>
);
}You own the pinned state: onPinnedChange tells you which item to move. Without customTrigger, clicking the whole item toggles it.
Animation
- The item glides to its new place on a spring (stiffness 320, damping 25, mass 0.8: a shared layout animation with a slight overshoot), above the other items while it moves. The others make room.
- Labels fade in and out when a group becomes empty or filled (220ms, ease-out), and slide with the items making room, on the items' spring.
- Triggers, and items without
customTrigger, shrink on press (scale 0.97) on a quick spring (the item's own spring for items). Nothing scales on hover. <AnimateUIProvider tempo>paces the default transitions.- Under reduced motion, items jump to their new place and don't scale.
API Reference
PinnedList
| Prop | Type | Default |
|---|---|---|
onPinnedChange? | (id: string) => void | - |
children | React.ReactNode | - |
...props? | HTMLMotionProps<'div'> | - |
PinnedListPinned, PinnedListUnpinned, PinnedListItems
Plain divs that structure the groups. They take div props.
PinnedListLabel
| Prop | Type | Default |
|---|---|---|
asChild? | boolean | false |
hide? | boolean | false |
transition? | Transition | { duration: 0.22, ease: [0.23, 1, 0.32, 1], layout: { type: 'spring', stiffness: 320, damping: 25, mass: 0.8 } } |
...props? | HTMLMotionProps<'p'> | - |
PinnedListItem
| Prop | Type | Default |
|---|---|---|
asChild? | boolean | false |
id | string | - |
customTrigger? | boolean | false |
transition? | Transition | { type: 'spring', stiffness: 320, damping: 25, mass: 0.8 } |
children | React.ReactNode | - |
...props? | HTMLMotionProps<'div'> | - |
PinnedListTrigger
A button that pins or unpins its item. Give it an accessible label.
| Prop | Type | Default |
|---|---|---|
asChild? | boolean | false |
...props? | HTMLMotionProps<'button'> | - |
usePinnedList, usePinnedListItem
usePinnedList returns movingId (the item moving between the lists), setMovingId and onPinnedChange. usePinnedListItem returns the item's id.
Credits
- Based on @arhamkhnz's Pin List.