Components

Share & Permissions Popover

A Notion-style share popover that is three views in one floating surface: the share list, a per-person role picker, and link settings. Each view declares its own natural width, so swapping panels morphs the popover on both axes - width and height are always a measured px easing between views (the height:auto illusion, twice), while panels slide directionally with a breadcrumb back header. The focus trap survives the swaps: Tab cycles the live panel only, pushing into the role picker lands focus on the current role, popping back returns it to the exact row that opened it, and closing hands it back to the Share button. Escape backs out one layer at a time. The popover scales in from under the trigger (origin-aware, never center) and stays mounted so open and close are interruptible. Tick Loose focus and Tab quietly walks out of the popover - the trap is the difference.

Q3 Launch PlanEdited 2h ago

Popover: closed · 4 people · Link: Invited people only

Install

npx moumenlab add share-permissions-popover

For AI

Open .md

Usage

"use client";

import SharePopover from "./share-permissions-popover";

// Seed it with your collaborators and wire onShare to persist every change
// (invite / role / remove / link scope).
export default function SharePopoverExample() {
  return (
    <SharePopover
      docTitle="Q3 Launch Plan"
      owner={{ name: "Moumen Soliman", email: "moumen@acme.co" }}
      people={[
        { id: "sarah", name: "Sarah Chen", email: "sarah@acme.co" },
        { id: "omar", name: "Omar Farouk", email: "omar@acme.co" },
        { id: "june", name: "June Park", email: "june@acme.co" },
      ]}
      initialRoles={{ sarah: "full", omar: "edit", june: "view" }}
      onShare={(event) => console.log(event.type, event.detail)}
    />
  );
}

Story

  1. A job requirement, and a sensitive one

    Another job requirement, and the most sensitive surface of them all: sharing and permissions. This popover hands out access to real documents and real people. A wrong click here is not a visual glitch, it is someone getting access they shouldn't have, and that risk shaped the interaction rules before any visuals.

  2. Nothing fires on the row

    The guard: no click by mistake. A fully clickable row is one stray click away from changing someone's access, so here the rows themselves are inert. Every action lives on a specific button: a person's role opens through its own small control at the row's edge, and clicking anywhere else on the person does exactly nothing.

  3. Three views, one surface

    The share list, a person's role picker and the link settings are three different-sized panels inside one floating element. The frame measures each view and eases its width and height between them, so swapping reads as one surface changing shape, not three popovers taking turns. Escape backs out one layer at a time.

  4. The animations.dev pattern

    For the motion I followed the steps/feedback popover pattern from animations.dev: push slides the next view in from the right, pop returns from the left, and the size morph carries the change. The focus trap survives every swap, handing focus somewhere sensible: the current role on push, the row's button on pop, the trigger on close.

References

  • animations.dev by Emil Kowalski: the steps/feedback popover this component's motion follows: the directional slide between views inside one frame that eases its own size.