Skip to content

Dynamic Island component#26

Merged
starc007 merged 9 commits into
mainfrom
feat/dynamic-island
Jun 10, 2026
Merged

Dynamic Island component#26
starc007 merged 9 commits into
mainfrom
feat/dynamic-island

Conversation

@starc007

Copy link
Copy Markdown
Owner

What

New motion component: iOS-style Dynamic Island. Pill morphs between live activity views (call, timer, music in the preview) with Apple-style physics.

How it works

  • Shell springs real width/height/borderRadius toward the measured natural size of the active content (ResizeObserver + w-max sizer). No transform scaling, so slots are never distorted while the shell resizes; this was the fix for collapse flicker after two layout-prop attempts
  • Compact pill uses real iPhone proportions (126 x 37). Everything blooms out of that capsule and gets sucked back into it: content anchored top center with scale and y drift from the pill line, shell pinned items-start so expansion unfurls downward
  • Direction-split springs with matching bounce character: expand 550/25, collapse 520/24 mass 0.45, so the squeeze has the same life as the bloom instead of snapping shut
  • Corner radius tweens between honest values (18.5 compact, 24 expanded) decoupled from the size spring; springing to a fake 999 radius glitched corners mid-resize
  • Exits are 100ms, blur-free, scaled into the pill core before the shrinking shell can clip them
  • role=status with aria-live announces view changes; reduced motion collapses everything to opacity crossfades and instant resize

API

Controlled, mirrors morphing-modal: view id plus compact slot, DynamicIslandView children. Registry bundle verified: ships with lib/ease.ts and lib/utils.ts only.

Also adds a gitignored /docs/ folder for local working notes.

Verification

  • bun run check green (typecheck, biome, 19 registry components validated)
  • Motion tuned over several rounds of hand testing in the browser: collapse flicker, corner glitch, press stall and heavy squeeze each diagnosed and fixed

starc007 added 9 commits June 10, 2026 23:52
iOS-style island pill that morphs between live activity views. Shell
resizes via layout animation with a slightly underdamped spring for
Apple-like overshoot; views crossfade with SPRING_SWAP blur slots.
Controlled API mirrors morphing-modal: view id prop plus a compact
slot shown when idle. role=status with aria-live announces view
changes; reduced motion collapses everything to opacity crossfades.

Preview demos call, timer (rolling countdown via NumberTicker) and
music states.
Three compounding artifacts on the shrink path:
- slots were plain children of the layout-animating shell, so they got
  stretched per-frame during the resize; layout="position" opts them
  into proper scale correction
- the exiting view is popped out of layout at its old size and the
  shell shrinks over it, so the slow blurred exit read as clipped
  flicker; exits are now 120ms, opacity + slight scale only
- shell spring damped a touch (28 -> 30, mass 0.65) so the overshoot
  does not wobble the clip on the way down
Layout-projection approach kept flickering on collapse: the shell
animated via transform scale, so slots needed per-frame scale
correction and the popped exiting view clashed with the shrinking
transform.

New approach eliminates transforms entirely. A ResizeObserver tracks
the natural size of the active content and the shell springs its real
width, height and borderRadius toward it (per motion docs: animating
border alongside layout defeats transform perf anyway, so animate the
box classically). Slots render at natural size inside a w-max sizer
and are never distorted. Padding moves from shell to slots so the
measurement includes it.
- compact pill gets real iPhone proportions (126 x 37) so every state
  blooms out of and collapses back into the same capsule
- shell springs split by direction: expansion is deliberately
  underdamped for a visible bloom overshoot, collapse snaps back
  tighter and faster
- content originates from the pill core: views enter at scale 0.6 with
  blur, slightly delayed so the shell leads; exits get sucked back to
  center in 100ms before the shrinking shell can clip them
- expanded radius eased to 24 for a rounded-rect look against the
  full-pill compact state
Content scaled from its own center while the shell grew symmetrically,
so expansion never read as coming out of the pill. Now everything is
anchored top center like the real island: the shell pins content to its
top edge, slots unfurl downward (origin top, slight y drift, scale
0.8) and exits get sucked back up into the pill. Preview reserves a
fixed top-aligned zone so the shell grows downward and the controls
stay put.
The corner flash on collapse came from springing borderRadius between a
fake 999 and 24: spring overshoot across that range glitches the
rendered corner mid-resize. Radius now tweens 200ms between honest
values (18.5, exactly half the 37px pill, and 24 expanded), decoupled
from the size spring so the corner shape never wobbles.

Snappier overall: shell springs stiffened (expand 550/25, collapse
620/32, same bounce ratios), content spring 560/28, exits trimmed to
80ms and opacity/blur tweens tightened.
Sticky feel had two sources. The shell waited for an async
ResizeObserver callback before springing (a 2-3 frame stall after every
press); the sizer is now measured synchronously in a layout effect on
each view swap, so shell and content launch in the same frame, with
ResizeObserver kept for async drift like ticker digits and font loads.
Content delays dropped (views 0, compact 20ms) so slots travel with the
shell instead of trailing it, and damping eased a touch on all three
springs for more follow-through.
Revert the press-stall experiment, then fix what heavy actually was:
the collapse spring was deliberately stiffer and more damped than the
expand (620/32 vs 550/25), which read as magnetic resistance on the
squeeze. Collapse now carries the same bounce character as the bloom
(520/24, mass 0.45) so the pill squeezes slightly past its size and
springs back instead of crunching shut.
@starc007 starc007 merged commit d306997 into main Jun 10, 2026
2 checks passed
@starc007 starc007 deleted the feat/dynamic-island branch June 10, 2026 19:02
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant