@evinvest/uikit 0.24.0 → 0.26.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (54) hide show
  1. package/README.md +147 -4
  2. package/dist/components/alert-dialog.js +1 -1
  3. package/dist/components/alert-dialog.js.map +1 -1
  4. package/dist/components/app-shell.js +85 -0
  5. package/dist/components/app-shell.js.map +1 -0
  6. package/dist/components/bottom-tab-bar.js +91 -0
  7. package/dist/components/bottom-tab-bar.js.map +1 -0
  8. package/dist/components/dialog.js +2 -1
  9. package/dist/components/dialog.js.map +1 -1
  10. package/dist/components/drawer.js +6 -4
  11. package/dist/components/drawer.js.map +1 -1
  12. package/dist/components/list-rows.js +73 -0
  13. package/dist/components/list-rows.js.map +1 -0
  14. package/dist/components/mobile-app-bar.js +51 -0
  15. package/dist/components/mobile-app-bar.js.map +1 -0
  16. package/dist/components/nav-badge.js +61 -0
  17. package/dist/components/nav-badge.js.map +1 -0
  18. package/dist/components/nav-link.js +50 -0
  19. package/dist/components/nav-link.js.map +1 -0
  20. package/dist/components/nav-model.js +55 -0
  21. package/dist/components/nav-model.js.map +1 -0
  22. package/dist/components/page-frame.js +92 -0
  23. package/dist/components/page-frame.js.map +1 -0
  24. package/dist/components/resource-error.js +47 -0
  25. package/dist/components/resource-error.js.map +1 -0
  26. package/dist/components/section-nav.js +86 -0
  27. package/dist/components/section-nav.js.map +1 -0
  28. package/dist/components/settled.js +19 -0
  29. package/dist/components/settled.js.map +1 -0
  30. package/dist/components/sheet.js +8 -2
  31. package/dist/components/sheet.js.map +1 -1
  32. package/dist/components/shell-icons.js +57 -0
  33. package/dist/components/shell-icons.js.map +1 -0
  34. package/dist/components/shell-nav.js +141 -0
  35. package/dist/components/shell-nav.js.map +1 -0
  36. package/dist/components/system-banner.js +53 -0
  37. package/dist/components/system-banner.js.map +1 -0
  38. package/dist/components/use-shell-nav.js +46 -0
  39. package/dist/components/use-shell-nav.js.map +1 -0
  40. package/dist/generated/alert-dialog.js +2 -2
  41. package/dist/generated/alert-dialog.js.map +1 -1
  42. package/dist/generated/dialog.js +3 -3
  43. package/dist/generated/dialog.js.map +1 -1
  44. package/dist/generated/drawer.js +1 -1
  45. package/dist/generated/drawer.js.map +1 -1
  46. package/dist/generated/sheet.js +3 -3
  47. package/dist/generated/sheet.js.map +1 -1
  48. package/dist/index.d.ts +451 -6
  49. package/dist/index.js +12 -0
  50. package/dist/primitives/focus-scope.js +22 -6
  51. package/dist/primitives/focus-scope.js.map +1 -1
  52. package/package.json +1 -1
  53. package/styles/theme.css +180 -1
  54. package/styles/tokens.css +182 -2
@@ -1 +1 @@
1
- {"version":3,"sources":["../../src/components/drawer.tsx"],"sourcesContent":["\"use client\";\n\nimport * as React from \"react\";\nimport { cn } from \"../lib/cn\";\nimport { useControllableState } from \"../primitives/use-controllable-state\";\nimport { useFocusScope } from \"../primitives/focus-scope\";\nimport { mergeRefs } from \"../primitives/merge-refs\";\nimport { Portal } from \"../primitives/portal\";\nimport { Slot } from \"../primitives/slot\";\nimport {\n DRAWER_BODY,\n DRAWER_CONTENT_BASE,\n DRAWER_DESCRIPTION,\n DRAWER_FOOTER,\n DRAWER_HANDLE,\n DRAWER_HEADER,\n DRAWER_OVERLAY,\n DRAWER_TITLE,\n drawerDirectionClasses,\n type DrawerDirection,\n} from \"../generated/drawer\";\n\nexport type { DrawerDirection };\n\n// Drag-to-dismiss tuning (pointer physics, TS-only — Rust stays render-only).\n// Values from Vaul: a release past 25% of the panel's size along the drag\n// axis, or a flick faster than 0.4px/ms, closes; anything short snaps back.\n// Drags are refused for the first 500ms after opening so the enter finishes.\nconst CLOSE_THRESHOLD = 0.25;\nconst VELOCITY_THRESHOLD = 0.4;\nconst DRAG_AFTER_OPEN_MS = 500;\n// The closing node is dropped on its exit `transitionend`; this is the safety\n// net for when that never fires (display:none ancestor, tab hidden, a full\n// drag that left the panel exactly at its off-screen transform). Just past\n// the 500ms transition in motion.css.\nconst EXIT_FALLBACK_MS = 600;\n\ninterface DrawerContextValue {\n open: boolean;\n setOpen: (next: boolean) => void;\n direction: DrawerDirection;\n}\n\nconst DrawerContext = React.createContext<DrawerContextValue | null>(null);\n\nfunction useDrawer(): DrawerContextValue {\n const ctx = React.useContext(DrawerContext);\n if (!ctx) throw new Error(\"Drawer parts must be used within <Drawer>\");\n return ctx;\n}\n\nexport interface DrawerProps {\n open?: boolean;\n defaultOpen?: boolean;\n onOpenChange?: (open: boolean) => void;\n direction?: DrawerDirection;\n children?: React.ReactNode;\n}\n\nexport function Drawer({\n open,\n defaultOpen = false,\n onOpenChange,\n direction = \"bottom\",\n children,\n}: DrawerProps) {\n const [isOpen, setOpen] = useControllableState<boolean>({\n ...(open !== undefined ? { value: open } : {}),\n defaultValue: defaultOpen,\n ...(onOpenChange ? { onChange: onOpenChange } : {}),\n });\n return (\n <DrawerContext.Provider value={{ open: isOpen, setOpen, direction }}>\n <div data-slot=\"drawer\">{children}</div>\n </DrawerContext.Provider>\n );\n}\n\nexport interface DrawerTriggerProps extends React.ComponentProps<\"button\"> {\n asChild?: boolean;\n}\n\nexport function DrawerTrigger({ asChild = false, onClick, ...props }: DrawerTriggerProps) {\n const { setOpen } = useDrawer();\n const Comp = asChild ? Slot : \"button\";\n return (\n <Comp\n data-slot=\"drawer-trigger\"\n onClick={(e: React.MouseEvent<HTMLButtonElement>) => {\n onClick?.(e);\n setOpen(true);\n }}\n {...(props as Record<string, unknown>)}\n />\n );\n}\n\nexport interface DrawerCloseProps extends React.ComponentProps<\"button\"> {\n asChild?: boolean;\n}\n\nexport function DrawerClose({ asChild = false, onClick, ...props }: DrawerCloseProps) {\n const { setOpen } = useDrawer();\n const Comp = asChild ? Slot : \"button\";\n return (\n <Comp\n data-slot=\"drawer-close\"\n onClick={(e: React.MouseEvent<HTMLButtonElement>) => {\n onClick?.(e);\n setOpen(false);\n }}\n {...(props as Record<string, unknown>)}\n />\n );\n}\n\n/**\n * Standalone scrim, redundant with the one {@link DrawerContent} renders (as in\n * shadcn). Mounted only while open; the animated exit lives on the content's\n * own scrim.\n */\nexport function DrawerOverlay({ className, ...props }: React.ComponentProps<\"div\">) {\n const { open, setOpen } = useDrawer();\n if (!open) return null;\n return (\n <div\n data-slot=\"drawer-overlay\"\n data-state=\"open\"\n onClick={() => setOpen(false)}\n className={cn(DRAWER_OVERLAY, className)}\n {...props}\n />\n );\n}\n\n// Positive delta = the finger moved towards the docked edge (bottom: down,\n// top: up, right: right, left: left), i.e. the closing direction.\nconst EDGE_SIGN: Record<DrawerDirection, 1 | -1> = {\n bottom: 1,\n top: -1,\n right: 1,\n left: -1,\n};\n\nfunction isVertical(direction: DrawerDirection): boolean {\n return direction === \"bottom\" || direction === \"top\";\n}\n\nfunction translateAlong(direction: DrawerDirection, px: number): string {\n return isVertical(direction) ? `translate3d(0, ${px}px, 0)` : `translate3d(${px}px, 0, 0)`;\n}\n\n// Vaul's over-drag curve: the panel barely moves past its docked edge. Only\n// starts displacing once the finger has gone ~6px (log(v + 1) > 2); before\n// that the curve is negative, which reads as \"stay home\".\nfunction dampen(px: number): number {\n return Math.max(8 * (Math.log(px + 1) - 2), 0);\n}\n\n// Vaul's `shouldDrag`, minus snap points. `towardsEdge` false = the user is\n// pulling the panel further open, which on a vertical sheet is the scroll\n// gesture — hand it to the scroller. The scroll walk is vertical-only: a\n// horizontal sheet never competes with its body's vertical scroll.\nfunction shouldDrag(\n target: EventTarget | null,\n panel: HTMLElement,\n direction: DrawerDirection,\n towardsEdge: boolean,\n): boolean {\n if (!(target instanceof Element)) return false;\n if (target.tagName === \"SELECT\") return false; // https://github.com/emilkowalski/vaul/issues/483\n if (target.closest(\"[data-vaul-no-drag]\")) return false;\n if (!isVertical(direction)) return true;\n if (!towardsEdge) return false;\n const selection = window.getSelection?.()?.toString();\n if (selection && selection.length > 0) return false;\n // any scroller between the finger and the panel that isn't at its top owns\n // the gesture; the user can scroll up and the next move re-asks\n let el: Element | null = target;\n while (el && el !== panel) {\n if (el.scrollHeight > el.clientHeight && el.scrollTop > 0) return false;\n el = el.parentElement;\n }\n return true;\n}\n\ninterface DragGesture {\n pointerId: number;\n // the element under the finger at pointerdown — once the panel captures the\n // pointer, every later event's `target` is the panel itself, and the scroll\n // walk in `shouldDrag` would never see the scroller it started in\n target: EventTarget | null;\n start: number; // clientX/Y along the axis\n time: number;\n size: number; // panel extent along the axis, for the close threshold + scrim fade\n allowed: boolean | null; // null = undecided; true sticks for the gesture (Vaul's isAllowedToDrag)\n delta: number;\n}\n\n// ── drag-to-dismiss (Vaul pointer physics) ────────────────────────────────\n// No React state on the hot path: the gesture lives in a ref and every move is\n// one inline `transform` write on the panel plus one `opacity` write on the\n// scrim. Not a CSS var: a custom-property change re-resolves inherited values\n// across the whole subtree, per move.\nfunction useDrawerDrag(opts: {\n panelRef: React.RefObject<HTMLDivElement | null>;\n scrimRef: React.RefObject<HTMLDivElement | null>;\n direction: DrawerDirection;\n open: boolean;\n present: boolean;\n openedAtRef: React.RefObject<number>;\n onClose: () => void;\n}) {\n const { panelRef, scrimRef, direction, open, present, openedAtRef, onClose } = opts;\n const dragRef = React.useRef<DragGesture | null>(null);\n\n // Native scroll suppression. Once a gesture is allowed, the browser must not\n // turn the same touch into a scroll/overscroll of a body sitting at its top —\n // that would pointercancel us mid-drag. Pointer events precede the compat\n // touch events, so `allowed` is already decided when this runs. React's\n // onTouchMove is passive, hence the manual listener.\n React.useEffect(() => {\n if (!present) return;\n const panel = panelRef.current;\n if (!panel) return;\n const onTouchMove = (e: TouchEvent) => {\n if (dragRef.current?.allowed) e.preventDefault();\n };\n panel.addEventListener(\"touchmove\", onTouchMove, { passive: false });\n return () => panel.removeEventListener(\"touchmove\", onTouchMove);\n }, [present, panelRef]);\n\n const axisPos = (e: React.PointerEvent) => (isVertical(direction) ? e.clientY : e.clientX);\n\n const onPointerDown = (e: React.PointerEvent<HTMLDivElement>) => {\n const panel = panelRef.current;\n // one gesture at a time: a second finger must not reset the first one's\n // state and leave the panel stranded mid-drag\n if (!panel || e.button > 0 || !open || dragRef.current) return;\n const target = e.target as HTMLElement;\n if (target.closest('[data-slot=\"drawer-close\"]')) return;\n // React bubbles portal events (a Select's listbox, a Popover) through the\n // tree; a drag can only start from the panel's own DOM\n if (!panel.contains(target)) return;\n const rect = panel.getBoundingClientRect();\n dragRef.current = {\n pointerId: e.pointerId,\n target,\n start: axisPos(e),\n time: Date.now(),\n size: isVertical(direction) ? rect.height : rect.width,\n allowed: null,\n delta: 0,\n };\n // Capture on the element under the finger, not the panel (Vaul does the\n // same): a click's target is the common ancestor of pointerdown and\n // pointerup, so capturing on the panel would steal every click from the\n // buttons and links inside. No preventDefault either — inputs inside the\n // sheet must still focus on tap.\n try {\n target.setPointerCapture?.(e.pointerId);\n } catch {\n // jsdom / unsupported — capture is a nice-to-have, the drag works without it\n }\n };\n\n const onPointerMove = (e: React.PointerEvent<HTMLDivElement>) => {\n const drag = dragRef.current;\n const panel = panelRef.current;\n if (!drag || !panel) return;\n const delta = (axisPos(e) - drag.start) * EDGE_SIGN[direction];\n drag.delta = delta;\n if (!drag.allowed) {\n if (Date.now() - openedAtRef.current < DRAG_AFTER_OPEN_MS) return;\n // refused on this move only — the user may scroll the body to its top\n // and carry on, so the gesture stays alive\n if (!shouldDrag(drag.target, panel, direction, delta > 0)) return;\n drag.allowed = true;\n panel.setAttribute(\"data-dragging\", \"true\"); // CSS drops the transitions for 1:1 tracking\n scrimRef.current?.setAttribute(\"data-dragging\", \"true\");\n }\n const scrim = scrimRef.current;\n if (delta < 0) {\n panel.style.transform = translateAlong(direction, -dampen(-delta) * EDGE_SIGN[direction]);\n if (scrim) scrim.style.opacity = \"1\";\n return;\n }\n panel.style.transform = translateAlong(direction, delta * EDGE_SIGN[direction]);\n if (scrim) {\n scrim.style.opacity = `${Math.min(1, Math.max(0, 1 - delta / drag.size))}`;\n }\n };\n\n // Release hands styling back to the CSS: with the inline transform gone and\n // data-dragging cleared in the same frame, the restored transition carries\n // the panel from wherever the finger left it — home on a snap-back, or (with\n // data-state=closed committed by `onClose` in the same frame) straight off.\n const endGesture = (e: React.PointerEvent<HTMLDivElement>, cancelled: boolean) => {\n const drag = dragRef.current;\n const panel = panelRef.current;\n if (!drag || !panel || e.pointerId !== drag.pointerId) return;\n dragRef.current = null;\n try {\n (drag.target as HTMLElement).releasePointerCapture?.(drag.pointerId);\n } catch {\n // jsdom / unsupported\n }\n if (!drag.allowed) return;\n panel.removeAttribute(\"data-dragging\");\n panel.style.transform = \"\";\n const scrim = scrimRef.current;\n if (scrim) {\n scrim.removeAttribute(\"data-dragging\");\n scrim.style.opacity = \"\";\n }\n if (cancelled || drag.delta <= 0) return;\n const dt = Date.now() - drag.time;\n const velocity = dt > 0 ? drag.delta / dt : 0;\n if (velocity > VELOCITY_THRESHOLD || drag.delta >= drag.size * CLOSE_THRESHOLD) {\n onClose();\n }\n };\n\n return {\n onPointerDown,\n onPointerMove,\n onPointerUp: (e: React.PointerEvent<HTMLDivElement>) => endGesture(e, false),\n onPointerCancel: (e: React.PointerEvent<HTMLDivElement>) => endGesture(e, true),\n };\n}\n\n/**\n * The panel plus its scrim. Presence outlives `open`: once it turns false the\n * panel flips to `data-state=\"closed\"` and is unmounted when its exit\n * `transform` transition ends (or the fallback timer fires). Re-opening\n * mid-exit just flips the state back and the same transition carries the\n * panel home. Drag-to-dismiss is TS-only pointer physics; opt a region out\n * with `data-vaul-no-drag`.\n */\nexport function DrawerContent({ className, children, ...props }: React.ComponentProps<\"div\">) {\n const { open, setOpen, direction } = useDrawer();\n // Derived during render so the panel is in the same commit that opened it —\n // the enter keyframe plays on insertion, no mount flip needed.\n const [present, setPresent] = React.useState(open);\n if (open && !present) setPresent(true);\n const closing = present && !open;\n\n const scopeRef = useFocusScope(open);\n const panelRef = React.useRef<HTMLDivElement | null>(null);\n const scrimRef = React.useRef<HTMLDivElement | null>(null);\n const openedAtRef = React.useRef(0);\n\n React.useEffect(() => {\n if (open) openedAtRef.current = Date.now();\n }, [open]);\n\n React.useEffect(() => {\n if (!open) return;\n const prev = document.body.style.overflow;\n document.body.style.overflow = \"hidden\";\n return () => {\n document.body.style.overflow = prev;\n };\n }, [open]);\n\n // Safety net for a missed exit transitionend; the cleanup is what cancels it\n // on re-open.\n React.useEffect(() => {\n if (!closing) return;\n const id = setTimeout(() => setPresent(false), EXIT_FALLBACK_MS);\n return () => clearTimeout(id);\n }, [closing]);\n\n const drag = useDrawerDrag({\n panelRef,\n scrimRef,\n direction,\n open,\n present,\n openedAtRef,\n onClose: () => setOpen(false),\n });\n\n if (!present) return null;\n const state = closing ? \"closed\" : \"open\";\n return (\n <Portal>\n <div\n ref={scrimRef}\n data-slot=\"drawer-overlay\"\n data-state={state}\n className={DRAWER_OVERLAY}\n onClick={() => setOpen(false)}\n />\n <div\n role=\"dialog\"\n aria-modal=\"true\"\n data-slot=\"drawer-content\"\n data-state={state}\n data-vaul-drawer-direction={direction}\n ref={mergeRefs(scopeRef, panelRef)}\n onKeyDown={(e) => {\n if (e.key === \"Escape\") setOpen(false);\n }}\n // transitionend bubbles from children (a hover on a button inside),\n // so pin it to the panel's own exit transform\n onTransitionEnd={(e) => {\n if (closing && e.propertyName === \"transform\" && e.target === e.currentTarget) {\n setPresent(false);\n }\n }}\n onPointerDown={drag.onPointerDown}\n onPointerMove={drag.onPointerMove}\n onPointerUp={drag.onPointerUp}\n onPointerCancel={drag.onPointerCancel}\n className={cn(DRAWER_CONTENT_BASE, drawerDirectionClasses[direction], className)}\n {...(props as Record<string, unknown>)}\n >\n {direction === \"bottom\" ? (\n <div\n data-slot=\"drawer-handle\"\n className={DRAWER_HANDLE}\n />\n ) : null}\n <div\n data-slot=\"drawer-body\"\n className={DRAWER_BODY}\n >\n {children}\n </div>\n </div>\n </Portal>\n );\n}\n\nexport function DrawerHeader({ className, ...props }: React.ComponentProps<\"div\">) {\n return (\n <div\n data-slot=\"drawer-header\"\n className={cn(DRAWER_HEADER, className)}\n {...props}\n />\n );\n}\n\nexport function DrawerFooter({ className, ...props }: React.ComponentProps<\"div\">) {\n return (\n <div\n data-slot=\"drawer-footer\"\n className={cn(DRAWER_FOOTER, className)}\n {...props}\n />\n );\n}\n\nexport function DrawerTitle({ className, ...props }: React.ComponentProps<\"div\">) {\n return (\n <div\n data-slot=\"drawer-title\"\n className={cn(DRAWER_TITLE, className)}\n {...props}\n />\n );\n}\n\nexport function DrawerDescription({ className, ...props }: React.ComponentProps<\"div\">) {\n return (\n <div\n data-slot=\"drawer-description\"\n className={cn(DRAWER_DESCRIPTION, className)}\n {...props}\n />\n );\n}\n"],"mappings":";AAyEM,cAiUA,YAjUA;AAvEN,YAAY,WAAW;AACvB,SAAS,UAAU;AACnB,SAAS,4BAA4B;AACrC,SAAS,qBAAqB;AAC9B,SAAS,iBAAiB;AAC1B,SAAS,cAAc;AACvB,SAAS,YAAY;AACrB;AAAA,EACE;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,OAEK;AAQP,MAAM,kBAAkB;AACxB,MAAM,qBAAqB;AAC3B,MAAM,qBAAqB;AAK3B,MAAM,mBAAmB;AAQzB,MAAM,gBAAgB,MAAM,cAAyC,IAAI;AAEzE,SAAS,YAAgC;AACvC,QAAM,MAAM,MAAM,WAAW,aAAa;AAC1C,MAAI,CAAC,IAAK,OAAM,IAAI,MAAM,2CAA2C;AACrE,SAAO;AACT;AAUO,SAAS,OAAO;AAAA,EACrB;AAAA,EACA,cAAc;AAAA,EACd;AAAA,EACA,YAAY;AAAA,EACZ;AACF,GAAgB;AACd,QAAM,CAAC,QAAQ,OAAO,IAAI,qBAA8B;AAAA,IACtD,GAAI,SAAS,SAAY,EAAE,OAAO,KAAK,IAAI,CAAC;AAAA,IAC5C,cAAc;AAAA,IACd,GAAI,eAAe,EAAE,UAAU,aAAa,IAAI,CAAC;AAAA,EACnD,CAAC;AACD,SACE,oBAAC,cAAc,UAAd,EAAuB,OAAO,EAAE,MAAM,QAAQ,SAAS,UAAU,GAChE,8BAAC,SAAI,aAAU,UAAU,UAAS,GACpC;AAEJ;AAMO,SAAS,cAAc,EAAE,UAAU,OAAO,SAAS,GAAG,MAAM,GAAuB;AACxF,QAAM,EAAE,QAAQ,IAAI,UAAU;AAC9B,QAAM,OAAO,UAAU,OAAO;AAC9B,SACE;AAAA,IAAC;AAAA;AAAA,MACC,aAAU;AAAA,MACV,SAAS,CAAC,MAA2C;AACnD,kBAAU,CAAC;AACX,gBAAQ,IAAI;AAAA,MACd;AAAA,MACC,GAAI;AAAA;AAAA,EACP;AAEJ;AAMO,SAAS,YAAY,EAAE,UAAU,OAAO,SAAS,GAAG,MAAM,GAAqB;AACpF,QAAM,EAAE,QAAQ,IAAI,UAAU;AAC9B,QAAM,OAAO,UAAU,OAAO;AAC9B,SACE;AAAA,IAAC;AAAA;AAAA,MACC,aAAU;AAAA,MACV,SAAS,CAAC,MAA2C;AACnD,kBAAU,CAAC;AACX,gBAAQ,KAAK;AAAA,MACf;AAAA,MACC,GAAI;AAAA;AAAA,EACP;AAEJ;AAOO,SAAS,cAAc,EAAE,WAAW,GAAG,MAAM,GAAgC;AAClF,QAAM,EAAE,MAAM,QAAQ,IAAI,UAAU;AACpC,MAAI,CAAC,KAAM,QAAO;AAClB,SACE;AAAA,IAAC;AAAA;AAAA,MACC,aAAU;AAAA,MACV,cAAW;AAAA,MACX,SAAS,MAAM,QAAQ,KAAK;AAAA,MAC5B,WAAW,GAAG,gBAAgB,SAAS;AAAA,MACtC,GAAG;AAAA;AAAA,EACN;AAEJ;AAIA,MAAM,YAA6C;AAAA,EACjD,QAAQ;AAAA,EACR,KAAK;AAAA,EACL,OAAO;AAAA,EACP,MAAM;AACR;AAEA,SAAS,WAAW,WAAqC;AACvD,SAAO,cAAc,YAAY,cAAc;AACjD;AAEA,SAAS,eAAe,WAA4B,IAAoB;AACtE,SAAO,WAAW,SAAS,IAAI,kBAAkB,EAAE,WAAW,eAAe,EAAE;AACjF;AAKA,SAAS,OAAO,IAAoB;AAClC,SAAO,KAAK,IAAI,KAAK,KAAK,IAAI,KAAK,CAAC,IAAI,IAAI,CAAC;AAC/C;AAMA,SAAS,WACP,QACA,OACA,WACA,aACS;AACT,MAAI,EAAE,kBAAkB,SAAU,QAAO;AACzC,MAAI,OAAO,YAAY,SAAU,QAAO;AACxC,MAAI,OAAO,QAAQ,qBAAqB,EAAG,QAAO;AAClD,MAAI,CAAC,WAAW,SAAS,EAAG,QAAO;AACnC,MAAI,CAAC,YAAa,QAAO;AACzB,QAAM,YAAY,OAAO,eAAe,GAAG,SAAS;AACpD,MAAI,aAAa,UAAU,SAAS,EAAG,QAAO;AAG9C,MAAI,KAAqB;AACzB,SAAO,MAAM,OAAO,OAAO;AACzB,QAAI,GAAG,eAAe,GAAG,gBAAgB,GAAG,YAAY,EAAG,QAAO;AAClE,SAAK,GAAG;AAAA,EACV;AACA,SAAO;AACT;AAoBA,SAAS,cAAc,MAQpB;AACD,QAAM,EAAE,UAAU,UAAU,WAAW,MAAM,SAAS,aAAa,QAAQ,IAAI;AAC/E,QAAM,UAAU,MAAM,OAA2B,IAAI;AAOrD,QAAM,UAAU,MAAM;AACpB,QAAI,CAAC,QAAS;AACd,UAAM,QAAQ,SAAS;AACvB,QAAI,CAAC,MAAO;AACZ,UAAM,cAAc,CAAC,MAAkB;AACrC,UAAI,QAAQ,SAAS,QAAS,GAAE,eAAe;AAAA,IACjD;AACA,UAAM,iBAAiB,aAAa,aAAa,EAAE,SAAS,MAAM,CAAC;AACnE,WAAO,MAAM,MAAM,oBAAoB,aAAa,WAAW;AAAA,EACjE,GAAG,CAAC,SAAS,QAAQ,CAAC;AAEtB,QAAM,UAAU,CAAC,MAA2B,WAAW,SAAS,IAAI,EAAE,UAAU,EAAE;AAElF,QAAM,gBAAgB,CAAC,MAA0C;AAC/D,UAAM,QAAQ,SAAS;AAGvB,QAAI,CAAC,SAAS,EAAE,SAAS,KAAK,CAAC,QAAQ,QAAQ,QAAS;AACxD,UAAM,SAAS,EAAE;AACjB,QAAI,OAAO,QAAQ,4BAA4B,EAAG;AAGlD,QAAI,CAAC,MAAM,SAAS,MAAM,EAAG;AAC7B,UAAM,OAAO,MAAM,sBAAsB;AACzC,YAAQ,UAAU;AAAA,MAChB,WAAW,EAAE;AAAA,MACb;AAAA,MACA,OAAO,QAAQ,CAAC;AAAA,MAChB,MAAM,KAAK,IAAI;AAAA,MACf,MAAM,WAAW,SAAS,IAAI,KAAK,SAAS,KAAK;AAAA,MACjD,SAAS;AAAA,MACT,OAAO;AAAA,IACT;AAMA,QAAI;AACF,aAAO,oBAAoB,EAAE,SAAS;AAAA,IACxC,QAAQ;AAAA,IAER;AAAA,EACF;AAEA,QAAM,gBAAgB,CAAC,MAA0C;AAC/D,UAAM,OAAO,QAAQ;AACrB,UAAM,QAAQ,SAAS;AACvB,QAAI,CAAC,QAAQ,CAAC,MAAO;AACrB,UAAM,SAAS,QAAQ,CAAC,IAAI,KAAK,SAAS,UAAU,SAAS;AAC7D,SAAK,QAAQ;AACb,QAAI,CAAC,KAAK,SAAS;AACjB,UAAI,KAAK,IAAI,IAAI,YAAY,UAAU,mBAAoB;AAG3D,UAAI,CAAC,WAAW,KAAK,QAAQ,OAAO,WAAW,QAAQ,CAAC,EAAG;AAC3D,WAAK,UAAU;AACf,YAAM,aAAa,iBAAiB,MAAM;AAC1C,eAAS,SAAS,aAAa,iBAAiB,MAAM;AAAA,IACxD;AACA,UAAM,QAAQ,SAAS;AACvB,QAAI,QAAQ,GAAG;AACb,YAAM,MAAM,YAAY,eAAe,WAAW,CAAC,OAAO,CAAC,KAAK,IAAI,UAAU,SAAS,CAAC;AACxF,UAAI,MAAO,OAAM,MAAM,UAAU;AACjC;AAAA,IACF;AACA,UAAM,MAAM,YAAY,eAAe,WAAW,QAAQ,UAAU,SAAS,CAAC;AAC9E,QAAI,OAAO;AACT,YAAM,MAAM,UAAU,GAAG,KAAK,IAAI,GAAG,KAAK,IAAI,GAAG,IAAI,QAAQ,KAAK,IAAI,CAAC,CAAC;AAAA,IAC1E;AAAA,EACF;AAMA,QAAM,aAAa,CAAC,GAAuC,cAAuB;AAChF,UAAM,OAAO,QAAQ;AACrB,UAAM,QAAQ,SAAS;AACvB,QAAI,CAAC,QAAQ,CAAC,SAAS,EAAE,cAAc,KAAK,UAAW;AACvD,YAAQ,UAAU;AAClB,QAAI;AACF,MAAC,KAAK,OAAuB,wBAAwB,KAAK,SAAS;AAAA,IACrE,QAAQ;AAAA,IAER;AACA,QAAI,CAAC,KAAK,QAAS;AACnB,UAAM,gBAAgB,eAAe;AACrC,UAAM,MAAM,YAAY;AACxB,UAAM,QAAQ,SAAS;AACvB,QAAI,OAAO;AACT,YAAM,gBAAgB,eAAe;AACrC,YAAM,MAAM,UAAU;AAAA,IACxB;AACA,QAAI,aAAa,KAAK,SAAS,EAAG;AAClC,UAAM,KAAK,KAAK,IAAI,IAAI,KAAK;AAC7B,UAAM,WAAW,KAAK,IAAI,KAAK,QAAQ,KAAK;AAC5C,QAAI,WAAW,sBAAsB,KAAK,SAAS,KAAK,OAAO,iBAAiB;AAC9E,cAAQ;AAAA,IACV;AAAA,EACF;AAEA,SAAO;AAAA,IACL;AAAA,IACA;AAAA,IACA,aAAa,CAAC,MAA0C,WAAW,GAAG,KAAK;AAAA,IAC3E,iBAAiB,CAAC,MAA0C,WAAW,GAAG,IAAI;AAAA,EAChF;AACF;AAUO,SAAS,cAAc,EAAE,WAAW,UAAU,GAAG,MAAM,GAAgC;AAC5F,QAAM,EAAE,MAAM,SAAS,UAAU,IAAI,UAAU;AAG/C,QAAM,CAAC,SAAS,UAAU,IAAI,MAAM,SAAS,IAAI;AACjD,MAAI,QAAQ,CAAC,QAAS,YAAW,IAAI;AACrC,QAAM,UAAU,WAAW,CAAC;AAE5B,QAAM,WAAW,cAAc,IAAI;AACnC,QAAM,WAAW,MAAM,OAA8B,IAAI;AACzD,QAAM,WAAW,MAAM,OAA8B,IAAI;AACzD,QAAM,cAAc,MAAM,OAAO,CAAC;AAElC,QAAM,UAAU,MAAM;AACpB,QAAI,KAAM,aAAY,UAAU,KAAK,IAAI;AAAA,EAC3C,GAAG,CAAC,IAAI,CAAC;AAET,QAAM,UAAU,MAAM;AACpB,QAAI,CAAC,KAAM;AACX,UAAM,OAAO,SAAS,KAAK,MAAM;AACjC,aAAS,KAAK,MAAM,WAAW;AAC/B,WAAO,MAAM;AACX,eAAS,KAAK,MAAM,WAAW;AAAA,IACjC;AAAA,EACF,GAAG,CAAC,IAAI,CAAC;AAIT,QAAM,UAAU,MAAM;AACpB,QAAI,CAAC,QAAS;AACd,UAAM,KAAK,WAAW,MAAM,WAAW,KAAK,GAAG,gBAAgB;AAC/D,WAAO,MAAM,aAAa,EAAE;AAAA,EAC9B,GAAG,CAAC,OAAO,CAAC;AAEZ,QAAM,OAAO,cAAc;AAAA,IACzB;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA,SAAS,MAAM,QAAQ,KAAK;AAAA,EAC9B,CAAC;AAED,MAAI,CAAC,QAAS,QAAO;AACrB,QAAM,QAAQ,UAAU,WAAW;AACnC,SACE,qBAAC,UACC;AAAA;AAAA,MAAC;AAAA;AAAA,QACC,KAAK;AAAA,QACL,aAAU;AAAA,QACV,cAAY;AAAA,QACZ,WAAW;AAAA,QACX,SAAS,MAAM,QAAQ,KAAK;AAAA;AAAA,IAC9B;AAAA,IACA;AAAA,MAAC;AAAA;AAAA,QACC,MAAK;AAAA,QACL,cAAW;AAAA,QACX,aAAU;AAAA,QACV,cAAY;AAAA,QACZ,8BAA4B;AAAA,QAC5B,KAAK,UAAU,UAAU,QAAQ;AAAA,QACjC,WAAW,CAAC,MAAM;AAChB,cAAI,EAAE,QAAQ,SAAU,SAAQ,KAAK;AAAA,QACvC;AAAA,QAGA,iBAAiB,CAAC,MAAM;AACtB,cAAI,WAAW,EAAE,iBAAiB,eAAe,EAAE,WAAW,EAAE,eAAe;AAC7E,uBAAW,KAAK;AAAA,UAClB;AAAA,QACF;AAAA,QACA,eAAe,KAAK;AAAA,QACpB,eAAe,KAAK;AAAA,QACpB,aAAa,KAAK;AAAA,QAClB,iBAAiB,KAAK;AAAA,QACtB,WAAW,GAAG,qBAAqB,uBAAuB,SAAS,GAAG,SAAS;AAAA,QAC9E,GAAI;AAAA,QAEJ;AAAA,wBAAc,WACb;AAAA,YAAC;AAAA;AAAA,cACC,aAAU;AAAA,cACV,WAAW;AAAA;AAAA,UACb,IACE;AAAA,UACJ;AAAA,YAAC;AAAA;AAAA,cACC,aAAU;AAAA,cACV,WAAW;AAAA,cAEV;AAAA;AAAA,UACH;AAAA;AAAA;AAAA,IACF;AAAA,KACF;AAEJ;AAEO,SAAS,aAAa,EAAE,WAAW,GAAG,MAAM,GAAgC;AACjF,SACE;AAAA,IAAC;AAAA;AAAA,MACC,aAAU;AAAA,MACV,WAAW,GAAG,eAAe,SAAS;AAAA,MACrC,GAAG;AAAA;AAAA,EACN;AAEJ;AAEO,SAAS,aAAa,EAAE,WAAW,GAAG,MAAM,GAAgC;AACjF,SACE;AAAA,IAAC;AAAA;AAAA,MACC,aAAU;AAAA,MACV,WAAW,GAAG,eAAe,SAAS;AAAA,MACrC,GAAG;AAAA;AAAA,EACN;AAEJ;AAEO,SAAS,YAAY,EAAE,WAAW,GAAG,MAAM,GAAgC;AAChF,SACE;AAAA,IAAC;AAAA;AAAA,MACC,aAAU;AAAA,MACV,WAAW,GAAG,cAAc,SAAS;AAAA,MACpC,GAAG;AAAA;AAAA,EACN;AAEJ;AAEO,SAAS,kBAAkB,EAAE,WAAW,GAAG,MAAM,GAAgC;AACtF,SACE;AAAA,IAAC;AAAA;AAAA,MACC,aAAU;AAAA,MACV,WAAW,GAAG,oBAAoB,SAAS;AAAA,MAC1C,GAAG;AAAA;AAAA,EACN;AAEJ;","names":[]}
1
+ {"version":3,"sources":["../../src/components/drawer.tsx"],"sourcesContent":["\"use client\";\n\nimport * as React from \"react\";\nimport { cn } from \"../lib/cn\";\nimport { useControllableState } from \"../primitives/use-controllable-state\";\nimport { useFocusScope } from \"../primitives/focus-scope\";\nimport { mergeRefs } from \"../primitives/merge-refs\";\nimport { Portal } from \"../primitives/portal\";\nimport { Slot } from \"../primitives/slot\";\nimport {\n DRAWER_BODY,\n DRAWER_CONTENT_BASE,\n DRAWER_DESCRIPTION,\n DRAWER_FOOTER,\n DRAWER_HANDLE,\n DRAWER_HEADER,\n DRAWER_OVERLAY,\n DRAWER_TITLE,\n drawerDirectionClasses,\n type DrawerDirection,\n} from \"../generated/drawer\";\n\nexport type { DrawerDirection };\n\n// Drag-to-dismiss tuning (pointer physics, TS-only — Rust stays render-only).\n// Values from Vaul: a release past 25% of the panel's size along the drag\n// axis, or a flick faster than 0.4px/ms, closes; anything short snaps back.\n// Drags are refused for the first 500ms after opening so the enter finishes.\nconst CLOSE_THRESHOLD = 0.25;\nconst VELOCITY_THRESHOLD = 0.4;\nconst DRAG_AFTER_OPEN_MS = 500;\n// The closing node is dropped on its exit `transitionend`; this is the safety\n// net for when that never fires (display:none ancestor, tab hidden, a full\n// drag that left the panel exactly at its off-screen transform). Just past\n// the 500ms transition in motion.css.\nconst EXIT_FALLBACK_MS = 600;\n\ninterface DrawerContextValue {\n open: boolean;\n setOpen: (next: boolean) => void;\n direction: DrawerDirection;\n triggerRef: React.RefObject<HTMLElement | null>;\n}\n\nconst DrawerContext = React.createContext<DrawerContextValue | null>(null);\n\nfunction useDrawer(): DrawerContextValue {\n const ctx = React.useContext(DrawerContext);\n if (!ctx) throw new Error(\"Drawer parts must be used within <Drawer>\");\n return ctx;\n}\n\nexport interface DrawerProps {\n open?: boolean;\n defaultOpen?: boolean;\n onOpenChange?: (open: boolean) => void;\n direction?: DrawerDirection;\n children?: React.ReactNode;\n}\n\nexport function Drawer({\n open,\n defaultOpen = false,\n onOpenChange,\n direction = \"bottom\",\n children,\n}: DrawerProps) {\n const [isOpen, setOpen] = useControllableState<boolean>({\n ...(open !== undefined ? { value: open } : {}),\n defaultValue: defaultOpen,\n ...(onOpenChange ? { onChange: onOpenChange } : {}),\n });\n const triggerRef = React.useRef<HTMLElement | null>(null);\n return (\n <DrawerContext.Provider value={{ open: isOpen, setOpen, direction, triggerRef }}>\n <div data-slot=\"drawer\">{children}</div>\n </DrawerContext.Provider>\n );\n}\n\nexport interface DrawerTriggerProps extends React.ComponentProps<\"button\"> {\n asChild?: boolean;\n}\n\nexport function DrawerTrigger({ asChild = false, onClick, ...props }: DrawerTriggerProps) {\n const { setOpen, triggerRef } = useDrawer();\n const Comp = asChild ? Slot : \"button\";\n return (\n <Comp\n data-slot=\"drawer-trigger\"\n ref={triggerRef as React.Ref<HTMLButtonElement>}\n onClick={(e: React.MouseEvent<HTMLButtonElement>) => {\n onClick?.(e);\n setOpen(true);\n }}\n {...(props as Record<string, unknown>)}\n />\n );\n}\n\nexport interface DrawerCloseProps extends React.ComponentProps<\"button\"> {\n asChild?: boolean;\n}\n\nexport function DrawerClose({ asChild = false, onClick, ...props }: DrawerCloseProps) {\n const { setOpen } = useDrawer();\n const Comp = asChild ? Slot : \"button\";\n return (\n <Comp\n data-slot=\"drawer-close\"\n onClick={(e: React.MouseEvent<HTMLButtonElement>) => {\n onClick?.(e);\n setOpen(false);\n }}\n {...(props as Record<string, unknown>)}\n />\n );\n}\n\n/**\n * Standalone scrim, redundant with the one {@link DrawerContent} renders (as in\n * shadcn). Mounted only while open; the animated exit lives on the content's\n * own scrim.\n */\nexport function DrawerOverlay({ className, ...props }: React.ComponentProps<\"div\">) {\n const { open, setOpen } = useDrawer();\n if (!open) return null;\n return (\n <div\n data-slot=\"drawer-overlay\"\n data-state=\"open\"\n onClick={() => setOpen(false)}\n className={cn(DRAWER_OVERLAY, className)}\n {...props}\n />\n );\n}\n\n// Positive delta = the finger moved towards the docked edge (bottom: down,\n// top: up, right: right, left: left), i.e. the closing direction.\nconst EDGE_SIGN: Record<DrawerDirection, 1 | -1> = {\n bottom: 1,\n top: -1,\n right: 1,\n left: -1,\n};\n\nfunction isVertical(direction: DrawerDirection): boolean {\n return direction === \"bottom\" || direction === \"top\";\n}\n\nfunction translateAlong(direction: DrawerDirection, px: number): string {\n return isVertical(direction) ? `translate3d(0, ${px}px, 0)` : `translate3d(${px}px, 0, 0)`;\n}\n\n// Vaul's over-drag curve: the panel barely moves past its docked edge. Only\n// starts displacing once the finger has gone ~6px (log(v + 1) > 2); before\n// that the curve is negative, which reads as \"stay home\".\nfunction dampen(px: number): number {\n return Math.max(8 * (Math.log(px + 1) - 2), 0);\n}\n\n// Vaul's `shouldDrag`, minus snap points. `towardsEdge` false = the user is\n// pulling the panel further open, which on a vertical sheet is the scroll\n// gesture — hand it to the scroller. The scroll walk is vertical-only: a\n// horizontal sheet never competes with its body's vertical scroll.\nfunction shouldDrag(\n target: EventTarget | null,\n panel: HTMLElement,\n direction: DrawerDirection,\n towardsEdge: boolean,\n): boolean {\n if (!(target instanceof Element)) return false;\n if (target.tagName === \"SELECT\") return false; // https://github.com/emilkowalski/vaul/issues/483\n if (target.closest(\"[data-vaul-no-drag]\")) return false;\n if (!isVertical(direction)) return true;\n if (!towardsEdge) return false;\n const selection = window.getSelection?.()?.toString();\n if (selection && selection.length > 0) return false;\n // any scroller between the finger and the panel that isn't at its top owns\n // the gesture; the user can scroll up and the next move re-asks\n let el: Element | null = target;\n while (el && el !== panel) {\n if (el.scrollHeight > el.clientHeight && el.scrollTop > 0) return false;\n el = el.parentElement;\n }\n return true;\n}\n\ninterface DragGesture {\n pointerId: number;\n // the element under the finger at pointerdown — once the panel captures the\n // pointer, every later event's `target` is the panel itself, and the scroll\n // walk in `shouldDrag` would never see the scroller it started in\n target: EventTarget | null;\n start: number; // clientX/Y along the axis\n time: number;\n size: number; // panel extent along the axis, for the close threshold + scrim fade\n allowed: boolean | null; // null = undecided; true sticks for the gesture (Vaul's isAllowedToDrag)\n delta: number;\n}\n\n// ── drag-to-dismiss (Vaul pointer physics) ────────────────────────────────\n// No React state on the hot path: the gesture lives in a ref and every move is\n// one inline `transform` write on the panel plus one `opacity` write on the\n// scrim. Not a CSS var: a custom-property change re-resolves inherited values\n// across the whole subtree, per move.\nfunction useDrawerDrag(opts: {\n panelRef: React.RefObject<HTMLDivElement | null>;\n scrimRef: React.RefObject<HTMLDivElement | null>;\n direction: DrawerDirection;\n open: boolean;\n present: boolean;\n openedAtRef: React.RefObject<number>;\n onClose: () => void;\n}) {\n const { panelRef, scrimRef, direction, open, present, openedAtRef, onClose } = opts;\n const dragRef = React.useRef<DragGesture | null>(null);\n\n // Native scroll suppression. Once a gesture is allowed, the browser must not\n // turn the same touch into a scroll/overscroll of a body sitting at its top —\n // that would pointercancel us mid-drag. Pointer events precede the compat\n // touch events, so `allowed` is already decided when this runs. React's\n // onTouchMove is passive, hence the manual listener.\n React.useEffect(() => {\n if (!present) return;\n const panel = panelRef.current;\n if (!panel) return;\n const onTouchMove = (e: TouchEvent) => {\n if (dragRef.current?.allowed) e.preventDefault();\n };\n panel.addEventListener(\"touchmove\", onTouchMove, { passive: false });\n return () => panel.removeEventListener(\"touchmove\", onTouchMove);\n }, [present, panelRef]);\n\n const axisPos = (e: React.PointerEvent) => (isVertical(direction) ? e.clientY : e.clientX);\n\n const onPointerDown = (e: React.PointerEvent<HTMLDivElement>) => {\n const panel = panelRef.current;\n // one gesture at a time: a second finger must not reset the first one's\n // state and leave the panel stranded mid-drag\n if (!panel || e.button > 0 || !open || dragRef.current) return;\n const target = e.target as HTMLElement;\n if (target.closest('[data-slot=\"drawer-close\"]')) return;\n // React bubbles portal events (a Select's listbox, a Popover) through the\n // tree; a drag can only start from the panel's own DOM\n if (!panel.contains(target)) return;\n const rect = panel.getBoundingClientRect();\n dragRef.current = {\n pointerId: e.pointerId,\n target,\n start: axisPos(e),\n time: Date.now(),\n size: isVertical(direction) ? rect.height : rect.width,\n allowed: null,\n delta: 0,\n };\n // Capture on the element under the finger, not the panel (Vaul does the\n // same): a click's target is the common ancestor of pointerdown and\n // pointerup, so capturing on the panel would steal every click from the\n // buttons and links inside. No preventDefault either — inputs inside the\n // sheet must still focus on tap.\n try {\n target.setPointerCapture?.(e.pointerId);\n } catch {\n // jsdom / unsupported — capture is a nice-to-have, the drag works without it\n }\n };\n\n const onPointerMove = (e: React.PointerEvent<HTMLDivElement>) => {\n const drag = dragRef.current;\n const panel = panelRef.current;\n if (!drag || !panel) return;\n const delta = (axisPos(e) - drag.start) * EDGE_SIGN[direction];\n drag.delta = delta;\n if (!drag.allowed) {\n if (Date.now() - openedAtRef.current < DRAG_AFTER_OPEN_MS) return;\n // refused on this move only — the user may scroll the body to its top\n // and carry on, so the gesture stays alive\n if (!shouldDrag(drag.target, panel, direction, delta > 0)) return;\n drag.allowed = true;\n panel.setAttribute(\"data-dragging\", \"true\"); // CSS drops the transitions for 1:1 tracking\n scrimRef.current?.setAttribute(\"data-dragging\", \"true\");\n }\n const scrim = scrimRef.current;\n if (delta < 0) {\n panel.style.transform = translateAlong(direction, -dampen(-delta) * EDGE_SIGN[direction]);\n if (scrim) scrim.style.opacity = \"1\";\n return;\n }\n panel.style.transform = translateAlong(direction, delta * EDGE_SIGN[direction]);\n if (scrim) {\n scrim.style.opacity = `${Math.min(1, Math.max(0, 1 - delta / drag.size))}`;\n }\n };\n\n // Release hands styling back to the CSS: with the inline transform gone and\n // data-dragging cleared in the same frame, the restored transition carries\n // the panel from wherever the finger left it — home on a snap-back, or (with\n // data-state=closed committed by `onClose` in the same frame) straight off.\n const endGesture = (e: React.PointerEvent<HTMLDivElement>, cancelled: boolean) => {\n const drag = dragRef.current;\n const panel = panelRef.current;\n if (!drag || !panel || e.pointerId !== drag.pointerId) return;\n dragRef.current = null;\n try {\n (drag.target as HTMLElement).releasePointerCapture?.(drag.pointerId);\n } catch {\n // jsdom / unsupported\n }\n if (!drag.allowed) return;\n panel.removeAttribute(\"data-dragging\");\n panel.style.transform = \"\";\n const scrim = scrimRef.current;\n if (scrim) {\n scrim.removeAttribute(\"data-dragging\");\n scrim.style.opacity = \"\";\n }\n if (cancelled || drag.delta <= 0) return;\n const dt = Date.now() - drag.time;\n const velocity = dt > 0 ? drag.delta / dt : 0;\n if (velocity > VELOCITY_THRESHOLD || drag.delta >= drag.size * CLOSE_THRESHOLD) {\n onClose();\n }\n };\n\n return {\n onPointerDown,\n onPointerMove,\n onPointerUp: (e: React.PointerEvent<HTMLDivElement>) => endGesture(e, false),\n onPointerCancel: (e: React.PointerEvent<HTMLDivElement>) => endGesture(e, true),\n };\n}\n\n/**\n * The panel plus its scrim. Presence outlives `open`: once it turns false the\n * panel flips to `data-state=\"closed\"` and is unmounted when its exit\n * `transform` transition ends (or the fallback timer fires). Re-opening\n * mid-exit just flips the state back and the same transition carries the\n * panel home. Drag-to-dismiss is TS-only pointer physics; opt a region out\n * with `data-vaul-no-drag`.\n */\nexport function DrawerContent({ className, children, ...props }: React.ComponentProps<\"div\">) {\n const { open, setOpen, direction, triggerRef } = useDrawer();\n // Derived during render so the panel is in the same commit that opened it —\n // the enter keyframe plays on insertion, no mount flip needed.\n const [present, setPresent] = React.useState(open);\n if (open && !present) setPresent(true);\n const closing = present && !open;\n\n const scopeRef = useFocusScope(open, { returnFocusTo: triggerRef });\n const panelRef = React.useRef<HTMLDivElement | null>(null);\n const scrimRef = React.useRef<HTMLDivElement | null>(null);\n const openedAtRef = React.useRef(0);\n\n React.useEffect(() => {\n if (open) openedAtRef.current = Date.now();\n }, [open]);\n\n React.useEffect(() => {\n if (!open) return;\n const prev = document.body.style.overflow;\n document.body.style.overflow = \"hidden\";\n return () => {\n document.body.style.overflow = prev;\n };\n }, [open]);\n\n // Safety net for a missed exit transitionend; the cleanup is what cancels it\n // on re-open.\n React.useEffect(() => {\n if (!closing) return;\n const id = setTimeout(() => setPresent(false), EXIT_FALLBACK_MS);\n return () => clearTimeout(id);\n }, [closing]);\n\n const drag = useDrawerDrag({\n panelRef,\n scrimRef,\n direction,\n open,\n present,\n openedAtRef,\n onClose: () => setOpen(false),\n });\n\n if (!present) return null;\n const state = closing ? \"closed\" : \"open\";\n return (\n <Portal>\n <div\n ref={scrimRef}\n data-slot=\"drawer-overlay\"\n data-state={state}\n className={DRAWER_OVERLAY}\n onClick={() => setOpen(false)}\n />\n <div\n role=\"dialog\"\n aria-modal=\"true\"\n data-slot=\"drawer-content\"\n data-state={state}\n data-vaul-drawer-direction={direction}\n ref={mergeRefs(scopeRef, panelRef)}\n onKeyDown={(e) => {\n if (e.key === \"Escape\") setOpen(false);\n }}\n // transitionend bubbles from children (a hover on a button inside),\n // so pin it to the panel's own exit transform\n onTransitionEnd={(e) => {\n if (closing && e.propertyName === \"transform\" && e.target === e.currentTarget) {\n setPresent(false);\n }\n }}\n onPointerDown={drag.onPointerDown}\n onPointerMove={drag.onPointerMove}\n onPointerUp={drag.onPointerUp}\n onPointerCancel={drag.onPointerCancel}\n className={cn(DRAWER_CONTENT_BASE, drawerDirectionClasses[direction], className)}\n {...(props as Record<string, unknown>)}\n >\n {direction === \"bottom\" ? (\n <div\n data-slot=\"drawer-handle\"\n className={DRAWER_HANDLE}\n />\n ) : null}\n <div\n data-slot=\"drawer-body\"\n className={DRAWER_BODY}\n >\n {children}\n </div>\n </div>\n </Portal>\n );\n}\n\nexport function DrawerHeader({ className, ...props }: React.ComponentProps<\"div\">) {\n return (\n <div\n data-slot=\"drawer-header\"\n className={cn(DRAWER_HEADER, className)}\n {...props}\n />\n );\n}\n\nexport function DrawerFooter({ className, ...props }: React.ComponentProps<\"div\">) {\n return (\n <div\n data-slot=\"drawer-footer\"\n className={cn(DRAWER_FOOTER, className)}\n {...props}\n />\n );\n}\n\nexport function DrawerTitle({ className, ...props }: React.ComponentProps<\"div\">) {\n return (\n <div\n data-slot=\"drawer-title\"\n className={cn(DRAWER_TITLE, className)}\n {...props}\n />\n );\n}\n\nexport function DrawerDescription({ className, ...props }: React.ComponentProps<\"div\">) {\n return (\n <div\n data-slot=\"drawer-description\"\n className={cn(DRAWER_DESCRIPTION, className)}\n {...props}\n />\n );\n}\n"],"mappings":"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","names":[]}
@@ -0,0 +1,73 @@
1
+ import { jsx, jsxs } from "react/jsx-runtime";
2
+ import * as React from "react";
3
+ import { cn } from "../lib/cn.js";
4
+ const LIST_ROWS = "flex w-full min-w-0 flex-col divide-y divide-border";
5
+ const listRowsVariants = {
6
+ plain: "",
7
+ card: "[--ev-list-row-x:1rem]"
8
+ };
9
+ function ListRows({ variant = "plain", className, ...props }) {
10
+ return /* @__PURE__ */ jsx(
11
+ "ul",
12
+ {
13
+ "data-slot": "list-rows",
14
+ "data-variant": variant,
15
+ className: cn(LIST_ROWS, listRowsVariants[variant], className),
16
+ ...props
17
+ }
18
+ );
19
+ }
20
+ const LIST_ROW = "flex min-w-0 items-center justify-between gap-3 px-[var(--ev-list-row-x,0px)] py-3";
21
+ function ListRow({ label, description, value, className, children, ...props }) {
22
+ return /* @__PURE__ */ jsxs(
23
+ "li",
24
+ {
25
+ "data-slot": "list-row",
26
+ className: cn(LIST_ROW, className),
27
+ ...props,
28
+ children: [
29
+ (label != null || description != null) && /* @__PURE__ */ jsx(ListRowLabel, { description, children: label }),
30
+ value != null && /* @__PURE__ */ jsx(ListRowValue, { children: value }),
31
+ children
32
+ ]
33
+ }
34
+ );
35
+ }
36
+ function ListRowLabel({ description, className, children, ...props }) {
37
+ return /* @__PURE__ */ jsxs(
38
+ "div",
39
+ {
40
+ "data-slot": "list-row-label",
41
+ className: cn("flex min-w-0 flex-1 flex-col gap-0.5", className),
42
+ ...props,
43
+ children: [
44
+ /* @__PURE__ */ jsx("span", { className: "text-sm font-medium text-ink", children }),
45
+ description != null && /* @__PURE__ */ jsx(
46
+ "span",
47
+ {
48
+ "data-slot": "list-row-description",
49
+ className: "text-xs leading-snug text-ink-soft",
50
+ children: description
51
+ }
52
+ )
53
+ ]
54
+ }
55
+ );
56
+ }
57
+ function ListRowValue({ className, ...props }) {
58
+ return /* @__PURE__ */ jsx(
59
+ "span",
60
+ {
61
+ "data-slot": "list-row-value",
62
+ className: cn("min-w-0 truncate text-right text-sm tabular-nums text-ink-soft", className),
63
+ ...props
64
+ }
65
+ );
66
+ }
67
+ export {
68
+ ListRow,
69
+ ListRowLabel,
70
+ ListRowValue,
71
+ ListRows
72
+ };
73
+ //# sourceMappingURL=list-rows.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../../src/components/list-rows.tsx"],"sourcesContent":["import * as React from \"react\";\nimport { cn } from \"../lib/cn\";\n\n// The phone form of a table: a list of label/value rows split by hairlines.\n// A table scrolled sideways on a phone hides the trailing columns — usually the\n// date and the action, the reason the list was opened. TS-only (README\n// \"Limitations\").\n\nexport type ListRowsVariant = \"plain\" | \"card\";\n\nconst LIST_ROWS = \"flex w-full min-w-0 flex-col divide-y divide-border\";\n\n// `plain` sits in a container that already pads (a padded card, a section);\n// `card` is edge to edge in a `TableCard`, so the rows pad themselves and the\n// hairlines run the card's full width. The inset travels as a custom property,\n// as the table's cell padding does, so a row's own `px-*` still wins.\nconst listRowsVariants: Record<ListRowsVariant, string> = {\n plain: \"\",\n card: \"[--ev-list-row-x:1rem]\",\n};\n\nexport interface ListRowsProps extends React.ComponentProps<\"ul\"> {\n variant?: ListRowsVariant;\n}\n\nexport function ListRows({ variant = \"plain\", className, ...props }: ListRowsProps) {\n return (\n <ul\n data-slot=\"list-rows\"\n data-variant={variant}\n className={cn(LIST_ROWS, listRowsVariants[variant], className)}\n {...props}\n />\n );\n}\n\nconst LIST_ROW = \"flex min-w-0 items-center justify-between gap-3 px-[var(--ev-list-row-x,0px)] py-3\";\n\n// `<li value>` is an ordinal for `<ol>`; in an unordered list of rows the name\n// is far more useful for the trailing figure.\nexport interface ListRowProps extends Omit<React.ComponentProps<\"li\">, \"value\"> {\n /** The row's title. Omit it (and `value`) to lay the row out yourself. */\n label?: React.ReactNode;\n /** A caption a step down under the label. */\n description?: React.ReactNode;\n /** Trailing value, right-aligned and truncated rather than pushing the row wide. */\n value?: React.ReactNode;\n}\n\n/**\n * One row: label (and description) leading, value trailing, then `children`\n * (a badge, an action). With neither `label` nor `value` the children are the\n * whole row.\n */\nexport function ListRow({ label, description, value, className, children, ...props }: ListRowProps) {\n return (\n <li\n data-slot=\"list-row\"\n className={cn(LIST_ROW, className)}\n {...props}\n >\n {(label != null || description != null) && (\n <ListRowLabel description={description}>{label}</ListRowLabel>\n )}\n {value != null && <ListRowValue>{value}</ListRowValue>}\n {children}\n </li>\n );\n}\n\nexport interface ListRowLabelProps extends Omit<React.ComponentProps<\"div\">, \"title\"> {\n description?: React.ReactNode;\n}\n\n/** The leading block on its own, for a row you compose by hand. */\nexport function ListRowLabel({ description, className, children, ...props }: ListRowLabelProps) {\n return (\n <div\n data-slot=\"list-row-label\"\n className={cn(\"flex min-w-0 flex-1 flex-col gap-0.5\", className)}\n {...props}\n >\n <span className=\"text-sm font-medium text-ink\">{children}</span>\n {description != null && (\n <span\n data-slot=\"list-row-description\"\n className=\"text-xs leading-snug text-ink-soft\"\n >\n {description}\n </span>\n )}\n </div>\n );\n}\n\n/**\n * The trailing value. It shares the label's type step, so the muted tone and\n * the lighter weight are what keep the label reading first; digits are tabular\n * so a figure in one row lines up with the next.\n */\nexport function ListRowValue({ className, ...props }: React.ComponentProps<\"span\">) {\n return (\n <span\n data-slot=\"list-row-value\"\n className={cn(\"min-w-0 truncate text-right text-sm tabular-nums text-ink-soft\", className)}\n {...props}\n />\n );\n}\n"],"mappings":"AA2BI,cA6BA,YA7BA;AA3BJ,YAAY,WAAW;AACvB,SAAS,UAAU;AASnB,MAAM,YAAY;AAMlB,MAAM,mBAAoD;AAAA,EACxD,OAAO;AAAA,EACP,MAAM;AACR;AAMO,SAAS,SAAS,EAAE,UAAU,SAAS,WAAW,GAAG,MAAM,GAAkB;AAClF,SACE;AAAA,IAAC;AAAA;AAAA,MACC,aAAU;AAAA,MACV,gBAAc;AAAA,MACd,WAAW,GAAG,WAAW,iBAAiB,OAAO,GAAG,SAAS;AAAA,MAC5D,GAAG;AAAA;AAAA,EACN;AAEJ;AAEA,MAAM,WAAW;AAkBV,SAAS,QAAQ,EAAE,OAAO,aAAa,OAAO,WAAW,UAAU,GAAG,MAAM,GAAiB;AAClG,SACE;AAAA,IAAC;AAAA;AAAA,MACC,aAAU;AAAA,MACV,WAAW,GAAG,UAAU,SAAS;AAAA,MAChC,GAAG;AAAA,MAEF;AAAA,kBAAS,QAAQ,eAAe,SAChC,oBAAC,gBAAa,aAA2B,iBAAM;AAAA,QAEhD,SAAS,QAAQ,oBAAC,gBAAc,iBAAM;AAAA,QACtC;AAAA;AAAA;AAAA,EACH;AAEJ;AAOO,SAAS,aAAa,EAAE,aAAa,WAAW,UAAU,GAAG,MAAM,GAAsB;AAC9F,SACE;AAAA,IAAC;AAAA;AAAA,MACC,aAAU;AAAA,MACV,WAAW,GAAG,wCAAwC,SAAS;AAAA,MAC9D,GAAG;AAAA,MAEJ;AAAA,4BAAC,UAAK,WAAU,gCAAgC,UAAS;AAAA,QACxD,eAAe,QACd;AAAA,UAAC;AAAA;AAAA,YACC,aAAU;AAAA,YACV,WAAU;AAAA,YAET;AAAA;AAAA,QACH;AAAA;AAAA;AAAA,EAEJ;AAEJ;AAOO,SAAS,aAAa,EAAE,WAAW,GAAG,MAAM,GAAiC;AAClF,SACE;AAAA,IAAC;AAAA;AAAA,MACC,aAAU;AAAA,MACV,WAAW,GAAG,kEAAkE,SAAS;AAAA,MACxF,GAAG;AAAA;AAAA,EACN;AAEJ;","names":[]}
@@ -0,0 +1,51 @@
1
+ "use client";
2
+ import { jsx, jsxs } from "react/jsx-runtime";
3
+ import * as React from "react";
4
+ import { cn } from "../lib/cn.js";
5
+ import { ChevronLeftIcon } from "./shell-icons.js";
6
+ const BACK = "-ml-1 flex size-6 shrink-0 items-center justify-center rounded-md text-ink outline-none focus-visible:ring-2 focus-visible:ring-ring";
7
+ const HIDE_FROM = { md: "md:hidden", lg: "lg:hidden" };
8
+ function MobileAppBar({
9
+ title,
10
+ back,
11
+ right,
12
+ linkComponent,
13
+ hideFrom = "lg",
14
+ labels = {},
15
+ className,
16
+ ...props
17
+ }) {
18
+ const backLabel = labels.back ?? "Back";
19
+ const Link = linkComponent ?? "a";
20
+ return /* @__PURE__ */ jsxs(
21
+ "header",
22
+ {
23
+ "data-slot": "mobile-app-bar",
24
+ "data-enter": "rise",
25
+ className: cn(
26
+ "sticky top-[var(--ev-shell-offset,0px)] z-30 flex items-center gap-2 border-b border-border bg-secondary px-4 pb-3.5 pt-4",
27
+ hideFrom && HIDE_FROM[hideFrom],
28
+ className
29
+ ),
30
+ ...props,
31
+ children: [
32
+ back && ("href" in back ? /* @__PURE__ */ jsx(Link, { href: back.href, "aria-label": backLabel, className: BACK, children: /* @__PURE__ */ jsx(ChevronLeftIcon, { className: "size-6" }) }) : /* @__PURE__ */ jsx("button", { type: "button", onClick: back.onClick, "aria-label": backLabel, className: BACK, children: /* @__PURE__ */ jsx(ChevronLeftIcon, { className: "size-6" }) })),
33
+ /* @__PURE__ */ jsx(
34
+ "h1",
35
+ {
36
+ className: cn(
37
+ "min-w-0 flex-1 truncate font-semibold text-ink",
38
+ back ? "text-center text-base" : "text-lg"
39
+ ),
40
+ children: title
41
+ }
42
+ ),
43
+ back && right === void 0 ? /* @__PURE__ */ jsx("span", { className: "size-6 shrink-0", "aria-hidden": true }) : right
44
+ ]
45
+ }
46
+ );
47
+ }
48
+ export {
49
+ MobileAppBar
50
+ };
51
+ //# sourceMappingURL=mobile-app-bar.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../../src/components/mobile-app-bar.tsx"],"sourcesContent":["\"use client\";\n\nimport * as React from \"react\";\nimport { cn } from \"../lib/cn\";\nimport type { ShellBreakpoint } from \"./app-shell\";\nimport { ChevronLeftIcon } from \"./shell-icons\";\n\nexport interface MobileAppBarLabels {\n /** Accessible name of the back control. Default `\"Back\"`. */\n back?: string;\n}\n\nexport type MobileAppBarBack = { href: string } | { onClick: () => void };\n\nexport interface MobileAppBarProps extends Omit<React.ComponentProps<\"header\">, \"title\"> {\n title: React.ReactNode;\n /** A pushed screen: a back link (`href`) or a back button (`onClick`); centres the title. */\n back?: MobileAppBarBack;\n /** Trailing action — Save, a menu. */\n right?: React.ReactNode;\n linkComponent?: React.ElementType;\n /** Hidden from this breakpoint up, where the page heading takes over. Default `lg`; `false` never hides. */\n hideFrom?: ShellBreakpoint | false;\n labels?: MobileAppBarLabels;\n}\n\n// A bare glyph has no border of its own, so it needs a rounded box for the ring to trace.\nconst BACK =\n \"-ml-1 flex size-6 shrink-0 items-center justify-center rounded-md text-ink outline-none focus-visible:ring-2 focus-visible:ring-ring\";\n\nconst HIDE_FROM = { md: \"md:hidden\", lg: \"lg:hidden\" } as const;\n\n/**\n * The bar that titles a screen on a phone. A root screen puts the title flush\n * left with an optional action; a pushed one (`back`) centres it between the\n * back control and a matching spacer. Sticks under `--ev-shell-offset` and\n * arrives with the screen (`data-enter=\"rise\"`), one step ahead of a\n * `PageFrame`'s sections.\n */\nexport function MobileAppBar({\n title,\n back,\n right,\n linkComponent,\n hideFrom = \"lg\",\n labels = {},\n className,\n ...props\n}: MobileAppBarProps) {\n const backLabel = labels.back ?? \"Back\";\n const Link = linkComponent ?? \"a\";\n return (\n <header\n data-slot=\"mobile-app-bar\"\n data-enter=\"rise\"\n className={cn(\n \"sticky top-[var(--ev-shell-offset,0px)] z-30 flex items-center gap-2 border-b border-border bg-secondary px-4 pb-3.5 pt-4\",\n hideFrom && HIDE_FROM[hideFrom],\n className,\n )}\n {...props}\n >\n {back &&\n (\"href\" in back ? (\n <Link href={back.href} aria-label={backLabel} className={BACK}>\n <ChevronLeftIcon className=\"size-6\" />\n </Link>\n ) : (\n <button type=\"button\" onClick={back.onClick} aria-label={backLabel} className={BACK}>\n <ChevronLeftIcon className=\"size-6\" />\n </button>\n ))}\n <h1\n className={cn(\n \"min-w-0 flex-1 truncate font-semibold text-ink\",\n back ? \"text-center text-base\" : \"text-lg\",\n )}\n >\n {title}\n </h1>\n {back && right === undefined ? <span className=\"size-6 shrink-0\" aria-hidden /> : right}\n </header>\n );\n}\n"],"mappings":";AAoDI,SAaQ,KAbR;AAlDJ,YAAY,WAAW;AACvB,SAAS,UAAU;AAEnB,SAAS,uBAAuB;AAsBhC,MAAM,OACJ;AAEF,MAAM,YAAY,EAAE,IAAI,aAAa,IAAI,YAAY;AAS9C,SAAS,aAAa;AAAA,EAC3B;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA,WAAW;AAAA,EACX,SAAS,CAAC;AAAA,EACV;AAAA,EACA,GAAG;AACL,GAAsB;AACpB,QAAM,YAAY,OAAO,QAAQ;AACjC,QAAM,OAAO,iBAAiB;AAC9B,SACE;AAAA,IAAC;AAAA;AAAA,MACC,aAAU;AAAA,MACV,cAAW;AAAA,MACX,WAAW;AAAA,QACT;AAAA,QACA,YAAY,UAAU,QAAQ;AAAA,QAC9B;AAAA,MACF;AAAA,MACC,GAAG;AAAA,MAEH;AAAA,iBACE,UAAU,OACT,oBAAC,QAAK,MAAM,KAAK,MAAM,cAAY,WAAW,WAAW,MACvD,8BAAC,mBAAgB,WAAU,UAAS,GACtC,IAEA,oBAAC,YAAO,MAAK,UAAS,SAAS,KAAK,SAAS,cAAY,WAAW,WAAW,MAC7E,8BAAC,mBAAgB,WAAU,UAAS,GACtC;AAAA,QAEJ;AAAA,UAAC;AAAA;AAAA,YACC,WAAW;AAAA,cACT;AAAA,cACA,OAAO,0BAA0B;AAAA,YACnC;AAAA,YAEC;AAAA;AAAA,QACH;AAAA,QACC,QAAQ,UAAU,SAAY,oBAAC,UAAK,WAAU,mBAAkB,eAAW,MAAC,IAAK;AAAA;AAAA;AAAA,EACpF;AAEJ;","names":[]}
@@ -0,0 +1,61 @@
1
+ import { jsx, jsxs } from "react/jsx-runtime";
2
+ import * as React from "react";
3
+ import { cn } from "../lib/cn.js";
4
+ const PILL = "rounded-full px-2 py-0.5 text-xs font-semibold tabular-nums";
5
+ const CORNER = "absolute -right-2.5 -top-1.5 flex h-4 min-w-4 items-center justify-center rounded-full bg-primary px-1 text-xs font-semibold leading-none tabular-nums text-on-primary";
6
+ const defaultLabel = (n) => `${n} new`;
7
+ function NavBadge({
8
+ count,
9
+ max = 99,
10
+ label = defaultLabel,
11
+ variant = "pill",
12
+ active = false,
13
+ className,
14
+ ...props
15
+ }) {
16
+ if (!(count > 0)) return null;
17
+ return /* @__PURE__ */ jsxs(
18
+ "span",
19
+ {
20
+ "data-slot": "nav-badge",
21
+ "data-variant": variant,
22
+ className: cn(
23
+ variant === "corner" ? CORNER : cn(PILL, active ? "bg-background text-ink" : "bg-primary-ink/15 text-primary-ink"),
24
+ className
25
+ ),
26
+ ...props,
27
+ children: [
28
+ /* @__PURE__ */ jsx("span", { "aria-hidden": true, children: count > max ? `${max}+` : count }),
29
+ /* @__PURE__ */ jsx("span", { className: "sr-only", children: label(count) })
30
+ ]
31
+ }
32
+ );
33
+ }
34
+ const DOT_TONE = {
35
+ primary: "bg-primary-ink",
36
+ positive: "bg-positive",
37
+ warn: "bg-accent-warn",
38
+ error: "bg-accent-error"
39
+ };
40
+ function NavDot({ tone = "primary", label, corner = false, className, ...props }) {
41
+ return /* @__PURE__ */ jsx(
42
+ "span",
43
+ {
44
+ "data-slot": "nav-dot",
45
+ "aria-hidden": label === void 0 ? true : void 0,
46
+ className: cn(
47
+ "inline-block size-2 shrink-0 rounded-full",
48
+ DOT_TONE[tone],
49
+ corner && "absolute -right-1 -top-0.5",
50
+ className
51
+ ),
52
+ ...props,
53
+ children: label !== void 0 && /* @__PURE__ */ jsx("span", { className: "sr-only", children: label })
54
+ }
55
+ );
56
+ }
57
+ export {
58
+ NavBadge,
59
+ NavDot
60
+ };
61
+ //# sourceMappingURL=nav-badge.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../../src/components/nav-badge.tsx"],"sourcesContent":["import * as React from \"react\";\nimport { cn } from \"../lib/cn\";\n\nexport type NavBadgeVariant = \"pill\" | \"corner\";\n\nexport interface NavBadgeProps extends Omit<React.ComponentProps<\"span\">, \"children\"> {\n count: number;\n /** Above this the badge shows `{max}+`. */\n max?: number;\n /**\n * The spoken text, given the REAL count — the one figure the cap throws away.\n * Default: `\"{n} new\"`.\n */\n label?: (count: number) => string;\n /**\n * `pill` sits in a row after the label (the rail); `corner` is the solid dot on\n * an icon's top-right corner (the tab bar) — its parent must be `relative`.\n */\n variant?: NavBadgeVariant;\n /** A pill on the marked row: inverted so it reads on the marker's fill. */\n active?: boolean;\n}\n\nconst PILL = \"rounded-full px-2 py-0.5 text-xs font-semibold tabular-nums\";\nconst CORNER =\n \"absolute -right-2.5 -top-1.5 flex h-4 min-w-4 items-center justify-center rounded-full bg-primary px-1 text-xs font-semibold leading-none tabular-nums text-on-primary\";\n\nconst defaultLabel = (n: number) => `${n} new`;\n\n/**\n * A count on a nav row or tab, capped so a neglected inbox cannot widen the\n * rail. Draws nothing for `0` or less.\n *\n * The visible figure is `aria-hidden` and the spoken one rides in an `sr-only`\n * span rather than an `aria-label`: a label on a plain span is not part of the\n * name the browser computes for the link around it, while text is — so the row\n * reads \"Notifications, 150 new\".\n */\nexport function NavBadge({\n count,\n max = 99,\n label = defaultLabel,\n variant = \"pill\",\n active = false,\n className,\n ...props\n}: NavBadgeProps) {\n if (!(count > 0)) return null;\n return (\n <span\n data-slot=\"nav-badge\"\n data-variant={variant}\n className={cn(\n variant === \"corner\"\n ? CORNER\n : cn(PILL, active ? \"bg-background text-ink\" : \"bg-primary-ink/15 text-primary-ink\"),\n className,\n )}\n {...props}\n >\n <span aria-hidden>{count > max ? `${max}+` : count}</span>\n <span className=\"sr-only\">{label(count)}</span>\n </span>\n );\n}\n\nexport type NavDotTone = \"primary\" | \"positive\" | \"warn\" | \"error\";\n\nconst DOT_TONE: Record<NavDotTone, string> = {\n primary: \"bg-primary-ink\",\n positive: \"bg-positive\",\n warn: \"bg-accent-warn\",\n error: \"bg-accent-error\",\n};\n\nexport interface NavDotProps extends Omit<React.ComponentProps<\"span\">, \"children\"> {\n tone?: NavDotTone;\n /** Spoken text; without one the dot is decoration and hidden from AT. */\n label?: string;\n /** Pin to an icon's top-right corner (parent `relative`), as the tab bar does. */\n corner?: boolean;\n}\n\n/** A presence dot — \"something here\", no figure. */\nexport function NavDot({ tone = \"primary\", label, corner = false, className, ...props }: NavDotProps) {\n return (\n <span\n data-slot=\"nav-dot\"\n aria-hidden={label === undefined ? true : undefined}\n className={cn(\n \"inline-block size-2 shrink-0 rounded-full\",\n DOT_TONE[tone],\n corner && \"absolute -right-1 -top-0.5\",\n className,\n )}\n {...props}\n >\n {label !== undefined && <span className=\"sr-only\">{label}</span>}\n </span>\n );\n}\n"],"mappings":"AAiDI,SAWE,KAXF;AAjDJ,YAAY,WAAW;AACvB,SAAS,UAAU;AAsBnB,MAAM,OAAO;AACb,MAAM,SACJ;AAEF,MAAM,eAAe,CAAC,MAAc,GAAG,CAAC;AAWjC,SAAS,SAAS;AAAA,EACvB;AAAA,EACA,MAAM;AAAA,EACN,QAAQ;AAAA,EACR,UAAU;AAAA,EACV,SAAS;AAAA,EACT;AAAA,EACA,GAAG;AACL,GAAkB;AAChB,MAAI,EAAE,QAAQ,GAAI,QAAO;AACzB,SACE;AAAA,IAAC;AAAA;AAAA,MACC,aAAU;AAAA,MACV,gBAAc;AAAA,MACd,WAAW;AAAA,QACT,YAAY,WACR,SACA,GAAG,MAAM,SAAS,2BAA2B,oCAAoC;AAAA,QACrF;AAAA,MACF;AAAA,MACC,GAAG;AAAA,MAEJ;AAAA,4BAAC,UAAK,eAAW,MAAE,kBAAQ,MAAM,GAAG,GAAG,MAAM,OAAM;AAAA,QACnD,oBAAC,UAAK,WAAU,WAAW,gBAAM,KAAK,GAAE;AAAA;AAAA;AAAA,EAC1C;AAEJ;AAIA,MAAM,WAAuC;AAAA,EAC3C,SAAS;AAAA,EACT,UAAU;AAAA,EACV,MAAM;AAAA,EACN,OAAO;AACT;AAWO,SAAS,OAAO,EAAE,OAAO,WAAW,OAAO,SAAS,OAAO,WAAW,GAAG,MAAM,GAAgB;AACpG,SACE;AAAA,IAAC;AAAA;AAAA,MACC,aAAU;AAAA,MACV,eAAa,UAAU,SAAY,OAAO;AAAA,MAC1C,WAAW;AAAA,QACT;AAAA,QACA,SAAS,IAAI;AAAA,QACb,UAAU;AAAA,QACV;AAAA,MACF;AAAA,MACC,GAAG;AAAA,MAEH,oBAAU,UAAa,oBAAC,UAAK,WAAU,WAAW,iBAAM;AAAA;AAAA,EAC3D;AAEJ;","names":[]}
@@ -0,0 +1,50 @@
1
+ "use client";
2
+ import { jsx } from "react/jsx-runtime";
3
+ import * as React from "react";
4
+ import { anchorTargetProps, isPlainLeftClick } from "./nav-model.js";
5
+ function NavLink({
6
+ item,
7
+ current,
8
+ pending,
9
+ linkComponent,
10
+ markPending,
11
+ onItemIntent,
12
+ onNavigate,
13
+ className,
14
+ title,
15
+ children
16
+ }) {
17
+ if (item.disabled) {
18
+ return /* @__PURE__ */ jsx("span", { "data-slot": "nav-link", "aria-disabled": "true", title, className, children });
19
+ }
20
+ const Comp = item.external ? "a" : linkComponent ?? "a";
21
+ const intent = onItemIntent && (() => onItemIntent(item));
22
+ return /* @__PURE__ */ jsx(
23
+ Comp,
24
+ {
25
+ "data-slot": "nav-link",
26
+ "data-pending": pending ? "true" : void 0,
27
+ href: item.href,
28
+ title,
29
+ "aria-current": current ? "page" : void 0,
30
+ className,
31
+ onPointerEnter: intent,
32
+ onFocus: intent,
33
+ onClick: (e) => {
34
+ if (item.external || !isPlainLeftClick(e)) return;
35
+ markPending(item.id);
36
+ onNavigate?.(item);
37
+ },
38
+ ...anchorTargetProps(item),
39
+ children
40
+ }
41
+ );
42
+ }
43
+ function labelTitle(label) {
44
+ return typeof label === "string" ? label : void 0;
45
+ }
46
+ export {
47
+ NavLink,
48
+ labelTitle
49
+ };
50
+ //# sourceMappingURL=nav-link.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../../src/components/nav-link.tsx"],"sourcesContent":["\"use client\";\n\nimport * as React from \"react\";\nimport { anchorTargetProps, isPlainLeftClick, type NavItem } from \"./nav-model\";\n\nexport interface NavLinkCallbacks {\n /** Hover or focus on an item — the moment to prefetch its route. */\n onItemIntent?: ((item: NavItem) => void) | undefined;\n /** A plain left click on an in-app item, after it is marked pending. */\n onNavigate?: ((item: NavItem) => void) | undefined;\n}\n\ninterface NavLinkProps extends NavLinkCallbacks {\n item: NavItem;\n current: boolean;\n pending: boolean;\n linkComponent: React.ElementType | undefined;\n markPending: (id: string) => void;\n className: string;\n title?: string | undefined;\n children: React.ReactNode;\n}\n\n/**\n * The anchor (or the disabled stand-in) under a rail row and a tab: picks the\n * element, wires the optimistic mark and the intent/navigate callbacks, and\n * carries `aria-current`, which is also what the rail's marker measures.\n */\nexport function NavLink({\n item,\n current,\n pending,\n linkComponent,\n markPending,\n onItemIntent,\n onNavigate,\n className,\n title,\n children,\n}: NavLinkProps) {\n if (item.disabled) {\n return (\n <span data-slot=\"nav-link\" aria-disabled=\"true\" title={title} className={className}>\n {children}\n </span>\n );\n }\n const Comp: React.ElementType = item.external ? \"a\" : (linkComponent ?? \"a\");\n const intent = onItemIntent && (() => onItemIntent(item));\n return (\n <Comp\n data-slot=\"nav-link\"\n data-pending={pending ? \"true\" : undefined}\n href={item.href}\n title={title}\n aria-current={current ? \"page\" : undefined}\n className={className}\n onPointerEnter={intent}\n onFocus={intent}\n onClick={(e: React.MouseEvent<HTMLAnchorElement>) => {\n if (item.external || !isPlainLeftClick(e)) return;\n markPending(item.id);\n onNavigate?.(item);\n }}\n {...anchorTargetProps(item)}\n >\n {children}\n </Comp>\n );\n}\n\n/** The row's tooltip: the label itself when it is plain text, so a truncated one stays readable. */\nexport function labelTitle(label: React.ReactNode): string | undefined {\n return typeof label === \"string\" ? label : undefined;\n}\n"],"mappings":";AA0CM;AAxCN,YAAY,WAAW;AACvB,SAAS,mBAAmB,wBAAsC;AAyB3D,SAAS,QAAQ;AAAA,EACtB;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AACF,GAAiB;AACf,MAAI,KAAK,UAAU;AACjB,WACE,oBAAC,UAAK,aAAU,YAAW,iBAAc,QAAO,OAAc,WAC3D,UACH;AAAA,EAEJ;AACA,QAAM,OAA0B,KAAK,WAAW,MAAO,iBAAiB;AACxE,QAAM,SAAS,iBAAiB,MAAM,aAAa,IAAI;AACvD,SACE;AAAA,IAAC;AAAA;AAAA,MACC,aAAU;AAAA,MACV,gBAAc,UAAU,SAAS;AAAA,MACjC,MAAM,KAAK;AAAA,MACX;AAAA,MACA,gBAAc,UAAU,SAAS;AAAA,MACjC;AAAA,MACA,gBAAgB;AAAA,MAChB,SAAS;AAAA,MACT,SAAS,CAAC,MAA2C;AACnD,YAAI,KAAK,YAAY,CAAC,iBAAiB,CAAC,EAAG;AAC3C,oBAAY,KAAK,EAAE;AACnB,qBAAa,IAAI;AAAA,MACnB;AAAA,MACC,GAAG,kBAAkB,IAAI;AAAA,MAEzB;AAAA;AAAA,EACH;AAEJ;AAGO,SAAS,WAAW,OAA4C;AACrE,SAAO,OAAO,UAAU,WAAW,QAAQ;AAC7C;","names":[]}
@@ -0,0 +1,55 @@
1
+ function pathOf(href) {
2
+ const cut = href.search(/[?#]/);
3
+ const path = cut === -1 ? href : href.slice(0, cut);
4
+ return path.length > 1 && path.endsWith("/") ? path.slice(0, -1) : path;
5
+ }
6
+ function underPrefix(pathname, prefix) {
7
+ if (prefix === "/") return pathname === "/";
8
+ return pathname === prefix || pathname.startsWith(`${prefix}/`);
9
+ }
10
+ const EXACT = Number.MAX_SAFE_INTEGER;
11
+ function navItemMatch(item, pathname) {
12
+ if (item.external || item.disabled) return null;
13
+ const path = pathOf(pathname);
14
+ const own = pathOf(item.href);
15
+ const match = item.match ?? "prefix";
16
+ let score = null;
17
+ if (typeof match === "function") score = match(path) ? own.length : null;
18
+ else if (path === own) score = EXACT;
19
+ else if (match === "prefix" && underPrefix(path, own)) score = own.length;
20
+ for (const extra of item.also ?? []) {
21
+ const p = pathOf(extra);
22
+ if (underPrefix(path, p) && (score === null || p.length > score)) score = p.length;
23
+ }
24
+ return score;
25
+ }
26
+ function isNavItemActive(item, pathname) {
27
+ return navItemMatch(item, pathname) !== null;
28
+ }
29
+ function resolveActiveNavItem(items, pathname) {
30
+ let best;
31
+ let bestScore = -1;
32
+ for (const item of items) {
33
+ const score = navItemMatch(item, pathname);
34
+ if (score !== null && score > bestScore) {
35
+ best = item;
36
+ bestScore = score;
37
+ }
38
+ }
39
+ return best;
40
+ }
41
+ function isPlainLeftClick(e) {
42
+ return !(e.defaultPrevented || e.metaKey || e.ctrlKey || e.shiftKey || e.altKey || e.button !== 0);
43
+ }
44
+ function anchorTargetProps(item) {
45
+ if (item.target === void 0) return {};
46
+ return item.target === "_blank" ? { target: item.target, rel: "noopener noreferrer" } : { target: item.target };
47
+ }
48
+ export {
49
+ anchorTargetProps,
50
+ isNavItemActive,
51
+ isPlainLeftClick,
52
+ navItemMatch,
53
+ resolveActiveNavItem
54
+ };
55
+ //# sourceMappingURL=nav-model.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../../src/components/nav-model.ts"],"sourcesContent":["import type * as React from \"react\";\n\n/**\n * How a {@link NavItem} decides it is the current one.\n *\n * - `\"prefix\"` (default) — the path is the item's own or lies beneath it, on a\n * segment boundary: `/invest` claims `/invest/arb` but not `/investors`. An\n * item at `/` is exact, or it would claim every page.\n * - `\"exact\"` — the path is the item's own and nothing beneath it.\n * - a function — whatever it answers for the path.\n */\nexport type NavMatch = \"exact\" | \"prefix\" | ((pathname: string) => boolean);\n\n/** State a row is rendered in; what `trailing` and `renderItem` are told. */\nexport interface NavItemState {\n active: boolean;\n /** Marked by a click whose navigation has not landed yet. */\n pending: boolean;\n}\n\n/** One destination, shared by the rail ({@link ShellNav}) and the tab bar. */\nexport interface NavItem {\n /** Stable key; also what `onNavigate` / `onItemIntent` hand back. */\n id: string;\n href: string;\n label: React.ReactNode;\n icon?: React.ComponentType<{ className?: string }>;\n /** A count (`NavBadge`, capped at 99+, `0` draws nothing) or your own node. */\n badge?: number | React.ReactNode;\n /** Anything after the label — a status chip; a function is told the row's state. */\n trailing?: React.ReactNode | ((state: NavItemState) => React.ReactNode);\n /**\n * Leaves the app: rendered as a plain `<a>` (never `linkComponent`), never\n * current, never marked pending — a `mailto:`, a docs site.\n */\n external?: boolean;\n /** Passed to the anchor; `_blank` gets `rel=\"noopener noreferrer\"`. */\n target?: React.HTMLAttributeAnchorTarget;\n match?: NavMatch;\n /**\n * More paths this item claims, prefix-matched — the one tab standing in for\n * a whole group of rail rows (`[\"/profile\", \"/notifications\"]`).\n */\n also?: readonly string[];\n /** Rendered, not focusable, never current. */\n disabled?: boolean;\n}\n\n/** A titled run of items; the rail draws one marker per group. */\nexport interface NavGroup {\n id: string;\n /** The eyebrow above the rows; also the list's accessible name. */\n label?: React.ReactNode;\n items: readonly NavItem[];\n}\n\nfunction pathOf(href: string): string {\n const cut = href.search(/[?#]/);\n const path = cut === -1 ? href : href.slice(0, cut);\n return path.length > 1 && path.endsWith(\"/\") ? path.slice(0, -1) : path;\n}\n\nfunction underPrefix(pathname: string, prefix: string): boolean {\n if (prefix === \"/\") return pathname === \"/\";\n return pathname === prefix || pathname.startsWith(`${prefix}/`);\n}\n\n// An exact hit outranks any prefix; among prefixes the longest wins. A function\n// match ranks as a prefix hit on the item's own href.\nconst EXACT = Number.MAX_SAFE_INTEGER;\n\n/**\n * How strongly `item` claims `pathname`, or `null` when it does not. Used to\n * pick ONE current item when several match — the rail and the tab bar each\n * draw a single marker, so two current rows is not a state they can show.\n */\nexport function navItemMatch(item: NavItem, pathname: string): number | null {\n if (item.external || item.disabled) return null;\n const path = pathOf(pathname);\n const own = pathOf(item.href);\n const match = item.match ?? \"prefix\";\n let score: number | null = null;\n if (typeof match === \"function\") score = match(path) ? own.length : null;\n else if (path === own) score = EXACT;\n else if (match === \"prefix\" && underPrefix(path, own)) score = own.length;\n for (const extra of item.also ?? []) {\n const p = pathOf(extra);\n if (underPrefix(path, p) && (score === null || p.length > score)) score = p.length;\n }\n return score;\n}\n\n/** Whether `item` claims `pathname` at all, regardless of its neighbours. */\nexport function isNavItemActive(item: NavItem, pathname: string): boolean {\n return navItemMatch(item, pathname) !== null;\n}\n\n/** The single current item for `pathname`: the strongest claim, ties to the first. */\nexport function resolveActiveNavItem(\n items: Iterable<NavItem>,\n pathname: string,\n): NavItem | undefined {\n let best: NavItem | undefined;\n let bestScore = -1;\n for (const item of items) {\n const score = navItemMatch(item, pathname);\n if (score !== null && score > bestScore) {\n best = item;\n bestScore = score;\n }\n }\n return best;\n}\n\n/**\n * A click the app should handle in place. A modified or non-primary click opens\n * the page elsewhere (a tab, a window, the context menu) and the router lets the\n * browser have it, so the current row stays current; `defaultPrevented` is the\n * same courtesy to anything upstream that claimed the click.\n */\nexport function isPlainLeftClick(e: React.MouseEvent): boolean {\n return !(e.defaultPrevented || e.metaKey || e.ctrlKey || e.shiftKey || e.altKey || e.button !== 0);\n}\n\n/** Props the rail and the tab bar hand a `linkComponent`; `next/link` takes all of them. */\nexport function anchorTargetProps(\n item: Pick<NavItem, \"target\">,\n): { target?: React.HTMLAttributeAnchorTarget; rel?: string } {\n if (item.target === undefined) return {};\n return item.target === \"_blank\"\n ? { target: item.target, rel: \"noopener noreferrer\" }\n : { target: item.target };\n}\n"],"mappings":"AAwDA,SAAS,OAAO,MAAsB;AACpC,QAAM,MAAM,KAAK,OAAO,MAAM;AAC9B,QAAM,OAAO,QAAQ,KAAK,OAAO,KAAK,MAAM,GAAG,GAAG;AAClD,SAAO,KAAK,SAAS,KAAK,KAAK,SAAS,GAAG,IAAI,KAAK,MAAM,GAAG,EAAE,IAAI;AACrE;AAEA,SAAS,YAAY,UAAkB,QAAyB;AAC9D,MAAI,WAAW,IAAK,QAAO,aAAa;AACxC,SAAO,aAAa,UAAU,SAAS,WAAW,GAAG,MAAM,GAAG;AAChE;AAIA,MAAM,QAAQ,OAAO;AAOd,SAAS,aAAa,MAAe,UAAiC;AAC3E,MAAI,KAAK,YAAY,KAAK,SAAU,QAAO;AAC3C,QAAM,OAAO,OAAO,QAAQ;AAC5B,QAAM,MAAM,OAAO,KAAK,IAAI;AAC5B,QAAM,QAAQ,KAAK,SAAS;AAC5B,MAAI,QAAuB;AAC3B,MAAI,OAAO,UAAU,WAAY,SAAQ,MAAM,IAAI,IAAI,IAAI,SAAS;AAAA,WAC3D,SAAS,IAAK,SAAQ;AAAA,WACtB,UAAU,YAAY,YAAY,MAAM,GAAG,EAAG,SAAQ,IAAI;AACnE,aAAW,SAAS,KAAK,QAAQ,CAAC,GAAG;AACnC,UAAM,IAAI,OAAO,KAAK;AACtB,QAAI,YAAY,MAAM,CAAC,MAAM,UAAU,QAAQ,EAAE,SAAS,OAAQ,SAAQ,EAAE;AAAA,EAC9E;AACA,SAAO;AACT;AAGO,SAAS,gBAAgB,MAAe,UAA2B;AACxE,SAAO,aAAa,MAAM,QAAQ,MAAM;AAC1C;AAGO,SAAS,qBACd,OACA,UACqB;AACrB,MAAI;AACJ,MAAI,YAAY;AAChB,aAAW,QAAQ,OAAO;AACxB,UAAM,QAAQ,aAAa,MAAM,QAAQ;AACzC,QAAI,UAAU,QAAQ,QAAQ,WAAW;AACvC,aAAO;AACP,kBAAY;AAAA,IACd;AAAA,EACF;AACA,SAAO;AACT;AAQO,SAAS,iBAAiB,GAA8B;AAC7D,SAAO,EAAE,EAAE,oBAAoB,EAAE,WAAW,EAAE,WAAW,EAAE,YAAY,EAAE,UAAU,EAAE,WAAW;AAClG;AAGO,SAAS,kBACd,MAC4D;AAC5D,MAAI,KAAK,WAAW,OAAW,QAAO,CAAC;AACvC,SAAO,KAAK,WAAW,WACnB,EAAE,QAAQ,KAAK,QAAQ,KAAK,sBAAsB,IAClD,EAAE,QAAQ,KAAK,OAAO;AAC5B;","names":[]}
@@ -0,0 +1,92 @@
1
+ import { Fragment, jsx, jsxs } from "react/jsx-runtime";
2
+ import * as React from "react";
3
+ import { cn } from "../lib/cn.js";
4
+ function SectionLabel({ as: Comp = "p", tone = "muted", className, ...props }) {
5
+ return /* @__PURE__ */ jsx(
6
+ Comp,
7
+ {
8
+ "data-slot": "section-label",
9
+ className: cn(
10
+ "text-xs font-semibold uppercase tracking-widest",
11
+ tone === "accent" ? "text-primary-ink" : "text-ink-soft",
12
+ className
13
+ ),
14
+ ...props
15
+ }
16
+ );
17
+ }
18
+ function PageHeading({ title, description, eyebrow, actions, className, ...props }) {
19
+ return /* @__PURE__ */ jsxs(
20
+ "header",
21
+ {
22
+ "data-slot": "page-heading",
23
+ className: cn("flex items-center justify-between gap-4", className),
24
+ ...props,
25
+ children: [
26
+ /* @__PURE__ */ jsxs("div", { className: "flex min-w-0 flex-col gap-1", children: [
27
+ eyebrow !== void 0 && /* @__PURE__ */ jsx(SectionLabel, { tone: "accent", children: eyebrow }),
28
+ /* @__PURE__ */ jsx("h1", { className: "text-2xl font-semibold leading-tight text-ink", children: title }),
29
+ description !== void 0 && /* @__PURE__ */ jsx("p", { className: "text-sm text-ink-soft", children: description })
30
+ ] }),
31
+ actions !== void 0 && /* @__PURE__ */ jsx("div", { className: "flex shrink-0 items-center gap-2.5", children: actions })
32
+ ]
33
+ }
34
+ );
35
+ }
36
+ const HEADING_FROM = { md: "hidden md:flex", lg: "hidden lg:flex" };
37
+ const PAD = {
38
+ md: "gap-4 px-4 pb-6 pt-5 md:gap-6 md:px-8 md:pb-7 md:pt-6",
39
+ lg: "gap-4 px-4 pb-6 pt-5 lg:gap-6 lg:px-8 lg:pb-7 lg:pt-6"
40
+ };
41
+ function PageFrame({
42
+ title,
43
+ description,
44
+ eyebrow,
45
+ actions,
46
+ appBar,
47
+ breakpoint = "lg",
48
+ width = "full",
49
+ headingClassName,
50
+ className,
51
+ style,
52
+ children,
53
+ ...props
54
+ }) {
55
+ const hasBar = appBar !== void 0 && appBar !== null && appBar !== false;
56
+ const enter = {
57
+ "--enter-step": "var(--ev-stagger-section, 50ms)",
58
+ ...hasBar ? { "--enter-delay": "var(--ev-stagger-section, 50ms)" } : {}
59
+ };
60
+ return /* @__PURE__ */ jsxs(Fragment, { children: [
61
+ appBar,
62
+ /* @__PURE__ */ jsxs(
63
+ "div",
64
+ {
65
+ "data-slot": "page-frame",
66
+ "data-enter": "stagger",
67
+ className: cn("flex flex-col", PAD[breakpoint], width === "content" && "max-w-5xl", className),
68
+ style: { ...enter, ...style },
69
+ ...props,
70
+ children: [
71
+ title !== void 0 && /* @__PURE__ */ jsx(
72
+ PageHeading,
73
+ {
74
+ title,
75
+ description,
76
+ eyebrow,
77
+ actions,
78
+ className: cn(hasBar && HEADING_FROM[breakpoint], headingClassName)
79
+ }
80
+ ),
81
+ children
82
+ ]
83
+ }
84
+ )
85
+ ] });
86
+ }
87
+ export {
88
+ PageFrame,
89
+ PageHeading,
90
+ SectionLabel
91
+ };
92
+ //# sourceMappingURL=page-frame.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../../src/components/page-frame.tsx"],"sourcesContent":["import * as React from \"react\";\nimport { cn } from \"../lib/cn\";\nimport type { ShellBreakpoint } from \"./app-shell\";\n\nexport type SectionLabelTone = \"muted\" | \"accent\";\n\nexport interface SectionLabelProps extends React.HTMLAttributes<HTMLElement> {\n as?: React.ElementType;\n /** `muted` labels a group or a section; `accent` announces a hero figure. */\n tone?: SectionLabelTone;\n}\n\n/** The tracked-uppercase label above a figure, a group or a section. */\nexport function SectionLabel({ as: Comp = \"p\", tone = \"muted\", className, ...props }: SectionLabelProps) {\n return (\n <Comp\n data-slot=\"section-label\"\n className={cn(\n \"text-xs font-semibold uppercase tracking-widest\",\n tone === \"accent\" ? \"text-primary-ink\" : \"text-ink-soft\",\n className,\n )}\n {...props}\n />\n );\n}\n\nexport interface PageHeadingProps extends Omit<React.ComponentProps<\"header\">, \"title\"> {\n title: React.ReactNode;\n /** What the screen is for, in a sentence. */\n description?: React.ReactNode;\n eyebrow?: React.ReactNode;\n /** The screen's own controls, on the right. */\n actions?: React.ReactNode;\n}\n\n/** The title row: eyebrow → title → description on the left, actions on the right. */\nexport function PageHeading({ title, description, eyebrow, actions, className, ...props }: PageHeadingProps) {\n return (\n <header\n data-slot=\"page-heading\"\n className={cn(\"flex items-center justify-between gap-4\", className)}\n {...props}\n >\n <div className=\"flex min-w-0 flex-col gap-1\">\n {eyebrow !== undefined && <SectionLabel tone=\"accent\">{eyebrow}</SectionLabel>}\n <h1 className=\"text-2xl font-semibold leading-tight text-ink\">{title}</h1>\n {description !== undefined && <p className=\"text-sm text-ink-soft\">{description}</p>}\n </div>\n {actions !== undefined && <div className=\"flex shrink-0 items-center gap-2.5\">{actions}</div>}\n </header>\n );\n}\n\nexport type PageFrameWidth = \"full\" | \"content\";\n\nexport interface PageFrameProps extends Omit<React.ComponentProps<\"div\">, \"title\"> {\n /** Omit when the screen's heading is its own composition. */\n title?: React.ReactNode;\n description?: React.ReactNode;\n eyebrow?: React.ReactNode;\n actions?: React.ReactNode;\n /**\n * The bar titling the screen on a phone (`MobileAppBar`). With one, the heading\n * shows from `breakpoint` up only and the sections arrive a step after the bar.\n */\n appBar?: React.ReactNode;\n /** Default `lg`. */\n breakpoint?: ShellBreakpoint;\n /** `full` fills the column (the rail already bounds it); `content` caps it at a reading measure. */\n width?: PageFrameWidth;\n headingClassName?: string;\n}\n\nconst HEADING_FROM = { md: \"hidden md:flex\", lg: \"hidden lg:flex\" } as const;\nconst PAD = {\n md: \"gap-4 px-4 pb-6 pt-5 md:gap-6 md:px-8 md:pb-7 md:pt-6\",\n lg: \"gap-4 px-4 pb-6 pt-5 lg:gap-6 lg:px-8 lg:pb-7 lg:pt-6\",\n} as const;\n\n/**\n * The frame every screen sits in: one inset, one section rhythm, one title scale,\n * so moving between screens never reads as moving between products.\n *\n * Its sections arrive in sequence by CSS alone (`data-enter=\"stagger\"`, see\n * motion.css): every direct child fades and rises `--ev-rise`, one\n * `--ev-stagger-section` after the previous, the heading first. Server-safe —\n * no motion library, no hydration needed for the entrance.\n */\nexport function PageFrame({\n title,\n description,\n eyebrow,\n actions,\n appBar,\n breakpoint = \"lg\",\n width = \"full\",\n headingClassName,\n className,\n style,\n children,\n ...props\n}: PageFrameProps) {\n const hasBar = appBar !== undefined && appBar !== null && appBar !== false;\n const enter = {\n \"--enter-step\": \"var(--ev-stagger-section, 50ms)\",\n ...(hasBar ? { \"--enter-delay\": \"var(--ev-stagger-section, 50ms)\" } : {}),\n };\n return (\n <>\n {appBar}\n <div\n data-slot=\"page-frame\"\n data-enter=\"stagger\"\n className={cn(\"flex flex-col\", PAD[breakpoint], width === \"content\" && \"max-w-5xl\", className)}\n style={{ ...enter, ...style }}\n {...props}\n >\n {title !== undefined && (\n <PageHeading\n title={title}\n description={description}\n eyebrow={eyebrow}\n actions={actions}\n className={cn(hasBar && HEADING_FROM[breakpoint], headingClassName)}\n />\n )}\n {children}\n </div>\n </>\n );\n}\n"],"mappings":"AAeI,SA8FA,UA9FA,KA6BE,YA7BF;AAfJ,YAAY,WAAW;AACvB,SAAS,UAAU;AAYZ,SAAS,aAAa,EAAE,IAAI,OAAO,KAAK,OAAO,SAAS,WAAW,GAAG,MAAM,GAAsB;AACvG,SACE;AAAA,IAAC;AAAA;AAAA,MACC,aAAU;AAAA,MACV,WAAW;AAAA,QACT;AAAA,QACA,SAAS,WAAW,qBAAqB;AAAA,QACzC;AAAA,MACF;AAAA,MACC,GAAG;AAAA;AAAA,EACN;AAEJ;AAYO,SAAS,YAAY,EAAE,OAAO,aAAa,SAAS,SAAS,WAAW,GAAG,MAAM,GAAqB;AAC3G,SACE;AAAA,IAAC;AAAA;AAAA,MACC,aAAU;AAAA,MACV,WAAW,GAAG,2CAA2C,SAAS;AAAA,MACjE,GAAG;AAAA,MAEJ;AAAA,6BAAC,SAAI,WAAU,+BACZ;AAAA,sBAAY,UAAa,oBAAC,gBAAa,MAAK,UAAU,mBAAQ;AAAA,UAC/D,oBAAC,QAAG,WAAU,iDAAiD,iBAAM;AAAA,UACpE,gBAAgB,UAAa,oBAAC,OAAE,WAAU,yBAAyB,uBAAY;AAAA,WAClF;AAAA,QACC,YAAY,UAAa,oBAAC,SAAI,WAAU,sCAAsC,mBAAQ;AAAA;AAAA;AAAA,EACzF;AAEJ;AAsBA,MAAM,eAAe,EAAE,IAAI,kBAAkB,IAAI,iBAAiB;AAClE,MAAM,MAAM;AAAA,EACV,IAAI;AAAA,EACJ,IAAI;AACN;AAWO,SAAS,UAAU;AAAA,EACxB;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA,aAAa;AAAA,EACb,QAAQ;AAAA,EACR;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA,GAAG;AACL,GAAmB;AACjB,QAAM,SAAS,WAAW,UAAa,WAAW,QAAQ,WAAW;AACrE,QAAM,QAAQ;AAAA,IACZ,gBAAgB;AAAA,IAChB,GAAI,SAAS,EAAE,iBAAiB,kCAAkC,IAAI,CAAC;AAAA,EACzE;AACA,SACE,iCACG;AAAA;AAAA,IACD;AAAA,MAAC;AAAA;AAAA,QACC,aAAU;AAAA,QACV,cAAW;AAAA,QACX,WAAW,GAAG,iBAAiB,IAAI,UAAU,GAAG,UAAU,aAAa,aAAa,SAAS;AAAA,QAC7F,OAAO,EAAE,GAAG,OAAO,GAAG,MAAM;AAAA,QAC3B,GAAG;AAAA,QAEH;AAAA,oBAAU,UACT;AAAA,YAAC;AAAA;AAAA,cACC;AAAA,cACA;AAAA,cACA;AAAA,cACA;AAAA,cACA,WAAW,GAAG,UAAU,aAAa,UAAU,GAAG,gBAAgB;AAAA;AAAA,UACpE;AAAA,UAED;AAAA;AAAA;AAAA,IACH;AAAA,KACF;AAEJ;","names":[]}
@@ -0,0 +1,47 @@
1
+ "use client";
2
+ import { jsx, jsxs } from "react/jsx-runtime";
3
+ import * as React from "react";
4
+ import { cn } from "../lib/cn.js";
5
+ import { Alert, AlertDescription, AlertTitle } from "./alert.js";
6
+ import { Button } from "./button.js";
7
+ import { RefreshIcon, TriangleAlertIcon } from "./shell-icons.js";
8
+ function ResourceError(props) {
9
+ if (props.variant === "alert") {
10
+ const { message: message2, title, labels: _labels, variant: _variant2, className: className2, ...rest2 } = props;
11
+ return /* @__PURE__ */ jsx("div", { "data-slot": "resource-error", "data-variant": "alert", className: className2, ...rest2, children: /* @__PURE__ */ jsxs(Alert, { variant: "destructive", children: [
12
+ /* @__PURE__ */ jsx(TriangleAlertIcon, { className: "size-4" }),
13
+ title !== void 0 && /* @__PURE__ */ jsx(AlertTitle, { children: title }),
14
+ /* @__PURE__ */ jsx(AlertDescription, { children: message2 })
15
+ ] }) });
16
+ }
17
+ const { message, onRetry, retrying = false, labels = {}, variant: _variant, title: _title, className, ...rest } = props;
18
+ const line = /* @__PURE__ */ jsxs("p", { className: "flex min-w-0 items-center gap-2 text-sm text-accent-error", children: [
19
+ /* @__PURE__ */ jsx(TriangleAlertIcon, { className: "size-4 shrink-0" }),
20
+ " ",
21
+ message
22
+ ] });
23
+ return /* @__PURE__ */ jsxs(
24
+ "div",
25
+ {
26
+ "data-slot": "resource-error",
27
+ "data-variant": "inline",
28
+ role: "alert",
29
+ className: cn(
30
+ onRetry && "flex flex-wrap items-center gap-3 rounded-lg border border-accent-error/40 bg-accent-error/10 px-3 py-2.5",
31
+ className
32
+ ),
33
+ ...rest,
34
+ children: [
35
+ line,
36
+ onRetry && /* @__PURE__ */ jsxs(Button, { type: "button", variant: "outline", size: "sm", disabled: retrying, onClick: onRetry, children: [
37
+ /* @__PURE__ */ jsx(RefreshIcon, { className: cn("size-4", retrying && "motion-safe:animate-spin") }),
38
+ labels.retry ?? "Try again"
39
+ ] })
40
+ ]
41
+ }
42
+ );
43
+ }
44
+ export {
45
+ ResourceError
46
+ };
47
+ //# sourceMappingURL=resource-error.js.map