@meetreeve/ui 0.6.3 → 0.8.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.
package/dist/index.d.mts CHANGED
@@ -42,7 +42,14 @@ interface Brand {
42
42
  declare function faviconUrl(domain: string | null | undefined): string | null;
43
43
  interface BrandGlyphProps {
44
44
  brand: Brand;
45
- size?: "sm" | "lg";
45
+ /**
46
+ * "sm"/"lg" are lockup sizes (a wordmark sits alongside the glyph
47
+ * elsewhere in the same surface — see MARK_CAP_RATIO). "xl" is for a
48
+ * bare, standalone glyph slot with no adjacent wordmark at all (e.g. a
49
+ * collapsed 56px icon rail, DEV-5331) — governed by GLYPH_CANVAS_FILL
50
+ * instead, same rule a favicon/avatar tile follows.
51
+ */
52
+ size?: "sm" | "lg" | "xl";
46
53
  /** Slightly lighter tint behind image sources (e.g. dropdown rows). */
47
54
  subtle?: boolean;
48
55
  className?: string;
@@ -341,6 +348,20 @@ interface AppSidebarProps {
341
348
  * `/studio` should NOT light up on `/studio/photo`).
342
349
  */
343
350
  isActive?: (href: string, activePath: string) => boolean;
351
+ /**
352
+ * Whether the desktop rail can collapse to a 56px icon rail (DEV-5331,
353
+ * porting AppNavRail's click-to-pin model into the package). Default
354
+ * `true`. `false` hides the toggle entirely — the rail then just stays at
355
+ * whatever `defaultCollapsed` says, with no way for the user to change it.
356
+ * Mobile is never affected either way (collapse is desktop-only).
357
+ */
358
+ collapsible?: boolean;
359
+ /** Initial collapsed state before any persisted value is read. Default `false`. */
360
+ defaultCollapsed?: boolean;
361
+ /** Fires with the new collapsed value whenever the user toggles the rail. */
362
+ onCollapsedChange?: (collapsed: boolean) => void;
363
+ /** localStorage key for the persisted collapsed preference. Default `"reeve-sidebar-collapsed"`. */
364
+ storageKey?: string;
344
365
  /** Merged onto the desktop `<aside>` itself — there is no extra wrapping
345
366
  * element (see the render note below on why that matters for height). */
346
367
  className?: string;
@@ -355,7 +376,7 @@ interface AppSidebarProps {
355
376
  * dependency, matching ResponsiveHeader's existing disclosure pattern in
356
377
  * this same package.
357
378
  */
358
- declare function AppSidebar({ ladder, modules, groupOrder, activePath, brand, topItem, menuLabel, ladderOptions, linkComponent, isActive, className, }: AppSidebarProps): react.JSX.Element;
379
+ declare function AppSidebar({ ladder, modules, groupOrder, activePath, brand, topItem, menuLabel, ladderOptions, linkComponent, isActive, collapsible, defaultCollapsed, onCollapsedChange, storageKey, className, }: AppSidebarProps): react.JSX.Element;
359
380
 
360
381
  /**
361
382
  * Minimal shape AppSidebar's fallback derivation needs from a module registry
@@ -436,6 +457,29 @@ interface FlyoutProps {
436
457
  * mirrors ResponsiveHeader's `linkComponent` prop (DEV-4429) exactly.
437
458
  */
438
459
  linkComponent?: ElementType;
460
+ /**
461
+ * Icon-only trigger for AppSidebar's collapsed (56px) rail (DEV-5331): the
462
+ * label collapses to a `sr-only` span (kept in the a11y tree) plus a native
463
+ * `title` tooltip — the package has no tooltip primitive and adding one
464
+ * (a Radix dep) is explicitly out of scope. The panel's own child rows are
465
+ * unaffected — they always show full labels regardless of this flag.
466
+ */
467
+ collapsed?: boolean;
468
+ /**
469
+ * Sibling-coordination hooks (DEV-5331 fix — Matt's live review found two
470
+ * folded-group flyouts open + overlapping at once, since each Flyout had
471
+ * zero awareness of its siblings). `onOpenChange` reports every open/
472
+ * closed transition of THIS instance's own hover/click intent — reported
473
+ * even while `forceClosed` is currently suppressing it, so a parent
474
+ * coordinator can learn "this one wants to open" and hand it the single
475
+ * active slot. `forceClosed` is set by that coordinator to immediately
476
+ * hide THIS instance (and reset its internal hover/click state) the
477
+ * instant a different sibling becomes the active one. Both optional and
478
+ * independent of each other — omitted, Flyout is fully standalone (its
479
+ * own state, unaware of any siblings), exactly as before.
480
+ */
481
+ onOpenChange?: (open: boolean) => void;
482
+ forceClosed?: boolean;
439
483
  className?: string;
440
484
  }
441
485
  /**
@@ -473,7 +517,7 @@ interface FlyoutProps {
473
517
  * than working around it. jsdom never computes real scrollable-overflow
474
518
  * geometry, so no unit test caught this — only an actual browser render did.
475
519
  */
476
- declare function Flyout({ label, icon, active, children, linkComponent, className }: FlyoutProps): react.JSX.Element;
520
+ declare function Flyout({ label, icon, active, children, linkComponent, collapsed, onOpenChange, forceClosed, className, }: FlyoutProps): react.JSX.Element;
477
521
 
478
522
  /**
479
523
  * ContextSwitcher (DEV-4408) — promoted out of reeve-frontend's
@@ -540,4 +584,71 @@ interface ContextSwitcherProps {
540
584
  }
541
585
  declare function ContextSwitcher({ items, activeRefId, onSelect, footer, placeholder, collapsed, side, align, contentClassName, }: ContextSwitcherProps): react.JSX.Element;
542
586
 
543
- export { type AppRungItem, AppSidebar, type AppSidebarBrand, type AppSidebarModuleDef, type AppSidebarProps, type AppSidebarTopItem, type Brand, BrandGlyph, type BrandGlyphProps, type ContextOrgGroup, ContextSwitcher, type ContextSwitcherItem, type ContextSwitcherProps, type FallbackModuleDef, Flyout, type FlyoutChild, type FlyoutProps, GLYPH_CANVAS_FILL, type GroupRungItem, type HeaderNavItem, LOCKUP_GAP, LOCKUP_ROW, MARK_CAP_RATIO, MARK_WEIGHT, REEVE_ACCENT, ResponsiveHeader, type ResponsiveHeaderProps, type RungItem, type SidebarLadder, type SidebarLadderParsed, type SidebarRung, type UseLadderRungOptions, WORDMARK_WEIGHT, deriveFallbackLadder, faviconUrl, groupByOrg, reconcileLadder, resolveLadder, useLadderRung, useResponsivePlaceholder, zSidebarLadder };
587
+ /**
588
+ * ConnectorPicker (DEV-5578 / CONN-3) — the one data-driven connect
589
+ * component in @meetreeve/ui. Renders a catalog of connectable entries from
590
+ * an injected client, owning the whole per-entry state machine
591
+ * (not-connected -> connecting -> connected, disconnect, inline per-tile
592
+ * errors, and a return-from-OAuth refresh). Every label is derived from the
593
+ * entry data the client returns — this file must never hardcode a specific
594
+ * provider's name (an explicit acceptance criterion); a host app supplies
595
+ * that entirely through `ConnectableEntry.name` / `.logo` / `.category`.
596
+ *
597
+ * Transport-injection precedent: `linkComponent` (DEV-4429/DEV-4391) —
598
+ * inject the transport, keep behavior in the package. Here the "transport"
599
+ * is the whole backend round-trip (list/connect/disconnect), injected as
600
+ * `client` (Matt's decision, 2026-07-25): Reeve passes one built on its own
601
+ * fetch client; a tenant clone passes ~10 lines of its own.
602
+ */
603
+ type ConnectableTransport = "composio" | "native";
604
+ type ConnectableCapability = "read" | "write" | "readwrite";
605
+ interface ConnectableEntry {
606
+ slug: string;
607
+ name: string;
608
+ logo: string | null;
609
+ category: string | null;
610
+ transport: ConnectableTransport;
611
+ capability: ConnectableCapability;
612
+ purposes: string[];
613
+ connection: {
614
+ status: "connected" | "not_connected";
615
+ connected_account_id: string | null;
616
+ since: string | null;
617
+ };
618
+ /** true = tenant has a live connection but the entry left the allowlist. */
619
+ deprecated: boolean;
620
+ }
621
+ interface ConnectorPickerClient {
622
+ listConnectable(opts: {
623
+ purpose?: string;
624
+ }): Promise<ConnectableEntry[]>;
625
+ /** composio -> open the returned connect_url; native may resolve with none. */
626
+ connect(entry: ConnectableEntry): Promise<{
627
+ connect_url?: string;
628
+ }>;
629
+ disconnect(entry: ConnectableEntry): Promise<void>;
630
+ }
631
+ /**
632
+ * Structurally identical to the app's own `DnaLens` (src/lib/dna-lens.ts) —
633
+ * this package must never import app code, so it declares its own copy
634
+ * rather than sharing the type.
635
+ */
636
+ type ConnectorPickerLens = "visitor" | "member" | "owner";
637
+ interface ConnectorPickerProps {
638
+ /** The injected transport adapter — required. */
639
+ client: ConnectorPickerClient;
640
+ /** Optional purpose slice, forwarded to `client.listConnectable`. */
641
+ purpose?: string;
642
+ /**
643
+ * Default "owner". "visitor"/"member" render nothing and make ZERO
644
+ * client calls — some host surfaces (e.g. a structurally owner-only
645
+ * knowledge page) have no visitor/member concept at all, and forcing them
646
+ * to synthesize a lens would import dead branches.
647
+ */
648
+ lens?: ConnectorPickerLens;
649
+ layout?: "tiles" | "list";
650
+ onConnected?: (entry: ConnectableEntry) => void;
651
+ }
652
+ declare function ConnectorPicker({ client, purpose, lens, layout, onConnected, }: ConnectorPickerProps): react.JSX.Element | null;
653
+
654
+ export { type AppRungItem, AppSidebar, type AppSidebarBrand, type AppSidebarModuleDef, type AppSidebarProps, type AppSidebarTopItem, type Brand, BrandGlyph, type BrandGlyphProps, type ConnectableCapability, type ConnectableEntry, type ConnectableTransport, ConnectorPicker, type ConnectorPickerClient, type ConnectorPickerLens, type ConnectorPickerProps, type ContextOrgGroup, ContextSwitcher, type ContextSwitcherItem, type ContextSwitcherProps, type FallbackModuleDef, Flyout, type FlyoutChild, type FlyoutProps, GLYPH_CANVAS_FILL, type GroupRungItem, type HeaderNavItem, LOCKUP_GAP, LOCKUP_ROW, MARK_CAP_RATIO, MARK_WEIGHT, REEVE_ACCENT, ResponsiveHeader, type ResponsiveHeaderProps, type RungItem, type SidebarLadder, type SidebarLadderParsed, type SidebarRung, type UseLadderRungOptions, WORDMARK_WEIGHT, deriveFallbackLadder, faviconUrl, groupByOrg, reconcileLadder, resolveLadder, useLadderRung, useResponsivePlaceholder, zSidebarLadder };
package/dist/index.d.ts CHANGED
@@ -42,7 +42,14 @@ interface Brand {
42
42
  declare function faviconUrl(domain: string | null | undefined): string | null;
43
43
  interface BrandGlyphProps {
44
44
  brand: Brand;
45
- size?: "sm" | "lg";
45
+ /**
46
+ * "sm"/"lg" are lockup sizes (a wordmark sits alongside the glyph
47
+ * elsewhere in the same surface — see MARK_CAP_RATIO). "xl" is for a
48
+ * bare, standalone glyph slot with no adjacent wordmark at all (e.g. a
49
+ * collapsed 56px icon rail, DEV-5331) — governed by GLYPH_CANVAS_FILL
50
+ * instead, same rule a favicon/avatar tile follows.
51
+ */
52
+ size?: "sm" | "lg" | "xl";
46
53
  /** Slightly lighter tint behind image sources (e.g. dropdown rows). */
47
54
  subtle?: boolean;
48
55
  className?: string;
@@ -341,6 +348,20 @@ interface AppSidebarProps {
341
348
  * `/studio` should NOT light up on `/studio/photo`).
342
349
  */
343
350
  isActive?: (href: string, activePath: string) => boolean;
351
+ /**
352
+ * Whether the desktop rail can collapse to a 56px icon rail (DEV-5331,
353
+ * porting AppNavRail's click-to-pin model into the package). Default
354
+ * `true`. `false` hides the toggle entirely — the rail then just stays at
355
+ * whatever `defaultCollapsed` says, with no way for the user to change it.
356
+ * Mobile is never affected either way (collapse is desktop-only).
357
+ */
358
+ collapsible?: boolean;
359
+ /** Initial collapsed state before any persisted value is read. Default `false`. */
360
+ defaultCollapsed?: boolean;
361
+ /** Fires with the new collapsed value whenever the user toggles the rail. */
362
+ onCollapsedChange?: (collapsed: boolean) => void;
363
+ /** localStorage key for the persisted collapsed preference. Default `"reeve-sidebar-collapsed"`. */
364
+ storageKey?: string;
344
365
  /** Merged onto the desktop `<aside>` itself — there is no extra wrapping
345
366
  * element (see the render note below on why that matters for height). */
346
367
  className?: string;
@@ -355,7 +376,7 @@ interface AppSidebarProps {
355
376
  * dependency, matching ResponsiveHeader's existing disclosure pattern in
356
377
  * this same package.
357
378
  */
358
- declare function AppSidebar({ ladder, modules, groupOrder, activePath, brand, topItem, menuLabel, ladderOptions, linkComponent, isActive, className, }: AppSidebarProps): react.JSX.Element;
379
+ declare function AppSidebar({ ladder, modules, groupOrder, activePath, brand, topItem, menuLabel, ladderOptions, linkComponent, isActive, collapsible, defaultCollapsed, onCollapsedChange, storageKey, className, }: AppSidebarProps): react.JSX.Element;
359
380
 
360
381
  /**
361
382
  * Minimal shape AppSidebar's fallback derivation needs from a module registry
@@ -436,6 +457,29 @@ interface FlyoutProps {
436
457
  * mirrors ResponsiveHeader's `linkComponent` prop (DEV-4429) exactly.
437
458
  */
438
459
  linkComponent?: ElementType;
460
+ /**
461
+ * Icon-only trigger for AppSidebar's collapsed (56px) rail (DEV-5331): the
462
+ * label collapses to a `sr-only` span (kept in the a11y tree) plus a native
463
+ * `title` tooltip — the package has no tooltip primitive and adding one
464
+ * (a Radix dep) is explicitly out of scope. The panel's own child rows are
465
+ * unaffected — they always show full labels regardless of this flag.
466
+ */
467
+ collapsed?: boolean;
468
+ /**
469
+ * Sibling-coordination hooks (DEV-5331 fix — Matt's live review found two
470
+ * folded-group flyouts open + overlapping at once, since each Flyout had
471
+ * zero awareness of its siblings). `onOpenChange` reports every open/
472
+ * closed transition of THIS instance's own hover/click intent — reported
473
+ * even while `forceClosed` is currently suppressing it, so a parent
474
+ * coordinator can learn "this one wants to open" and hand it the single
475
+ * active slot. `forceClosed` is set by that coordinator to immediately
476
+ * hide THIS instance (and reset its internal hover/click state) the
477
+ * instant a different sibling becomes the active one. Both optional and
478
+ * independent of each other — omitted, Flyout is fully standalone (its
479
+ * own state, unaware of any siblings), exactly as before.
480
+ */
481
+ onOpenChange?: (open: boolean) => void;
482
+ forceClosed?: boolean;
439
483
  className?: string;
440
484
  }
441
485
  /**
@@ -473,7 +517,7 @@ interface FlyoutProps {
473
517
  * than working around it. jsdom never computes real scrollable-overflow
474
518
  * geometry, so no unit test caught this — only an actual browser render did.
475
519
  */
476
- declare function Flyout({ label, icon, active, children, linkComponent, className }: FlyoutProps): react.JSX.Element;
520
+ declare function Flyout({ label, icon, active, children, linkComponent, collapsed, onOpenChange, forceClosed, className, }: FlyoutProps): react.JSX.Element;
477
521
 
478
522
  /**
479
523
  * ContextSwitcher (DEV-4408) — promoted out of reeve-frontend's
@@ -540,4 +584,71 @@ interface ContextSwitcherProps {
540
584
  }
541
585
  declare function ContextSwitcher({ items, activeRefId, onSelect, footer, placeholder, collapsed, side, align, contentClassName, }: ContextSwitcherProps): react.JSX.Element;
542
586
 
543
- export { type AppRungItem, AppSidebar, type AppSidebarBrand, type AppSidebarModuleDef, type AppSidebarProps, type AppSidebarTopItem, type Brand, BrandGlyph, type BrandGlyphProps, type ContextOrgGroup, ContextSwitcher, type ContextSwitcherItem, type ContextSwitcherProps, type FallbackModuleDef, Flyout, type FlyoutChild, type FlyoutProps, GLYPH_CANVAS_FILL, type GroupRungItem, type HeaderNavItem, LOCKUP_GAP, LOCKUP_ROW, MARK_CAP_RATIO, MARK_WEIGHT, REEVE_ACCENT, ResponsiveHeader, type ResponsiveHeaderProps, type RungItem, type SidebarLadder, type SidebarLadderParsed, type SidebarRung, type UseLadderRungOptions, WORDMARK_WEIGHT, deriveFallbackLadder, faviconUrl, groupByOrg, reconcileLadder, resolveLadder, useLadderRung, useResponsivePlaceholder, zSidebarLadder };
587
+ /**
588
+ * ConnectorPicker (DEV-5578 / CONN-3) — the one data-driven connect
589
+ * component in @meetreeve/ui. Renders a catalog of connectable entries from
590
+ * an injected client, owning the whole per-entry state machine
591
+ * (not-connected -> connecting -> connected, disconnect, inline per-tile
592
+ * errors, and a return-from-OAuth refresh). Every label is derived from the
593
+ * entry data the client returns — this file must never hardcode a specific
594
+ * provider's name (an explicit acceptance criterion); a host app supplies
595
+ * that entirely through `ConnectableEntry.name` / `.logo` / `.category`.
596
+ *
597
+ * Transport-injection precedent: `linkComponent` (DEV-4429/DEV-4391) —
598
+ * inject the transport, keep behavior in the package. Here the "transport"
599
+ * is the whole backend round-trip (list/connect/disconnect), injected as
600
+ * `client` (Matt's decision, 2026-07-25): Reeve passes one built on its own
601
+ * fetch client; a tenant clone passes ~10 lines of its own.
602
+ */
603
+ type ConnectableTransport = "composio" | "native";
604
+ type ConnectableCapability = "read" | "write" | "readwrite";
605
+ interface ConnectableEntry {
606
+ slug: string;
607
+ name: string;
608
+ logo: string | null;
609
+ category: string | null;
610
+ transport: ConnectableTransport;
611
+ capability: ConnectableCapability;
612
+ purposes: string[];
613
+ connection: {
614
+ status: "connected" | "not_connected";
615
+ connected_account_id: string | null;
616
+ since: string | null;
617
+ };
618
+ /** true = tenant has a live connection but the entry left the allowlist. */
619
+ deprecated: boolean;
620
+ }
621
+ interface ConnectorPickerClient {
622
+ listConnectable(opts: {
623
+ purpose?: string;
624
+ }): Promise<ConnectableEntry[]>;
625
+ /** composio -> open the returned connect_url; native may resolve with none. */
626
+ connect(entry: ConnectableEntry): Promise<{
627
+ connect_url?: string;
628
+ }>;
629
+ disconnect(entry: ConnectableEntry): Promise<void>;
630
+ }
631
+ /**
632
+ * Structurally identical to the app's own `DnaLens` (src/lib/dna-lens.ts) —
633
+ * this package must never import app code, so it declares its own copy
634
+ * rather than sharing the type.
635
+ */
636
+ type ConnectorPickerLens = "visitor" | "member" | "owner";
637
+ interface ConnectorPickerProps {
638
+ /** The injected transport adapter — required. */
639
+ client: ConnectorPickerClient;
640
+ /** Optional purpose slice, forwarded to `client.listConnectable`. */
641
+ purpose?: string;
642
+ /**
643
+ * Default "owner". "visitor"/"member" render nothing and make ZERO
644
+ * client calls — some host surfaces (e.g. a structurally owner-only
645
+ * knowledge page) have no visitor/member concept at all, and forcing them
646
+ * to synthesize a lens would import dead branches.
647
+ */
648
+ lens?: ConnectorPickerLens;
649
+ layout?: "tiles" | "list";
650
+ onConnected?: (entry: ConnectableEntry) => void;
651
+ }
652
+ declare function ConnectorPicker({ client, purpose, lens, layout, onConnected, }: ConnectorPickerProps): react.JSX.Element | null;
653
+
654
+ export { type AppRungItem, AppSidebar, type AppSidebarBrand, type AppSidebarModuleDef, type AppSidebarProps, type AppSidebarTopItem, type Brand, BrandGlyph, type BrandGlyphProps, type ConnectableCapability, type ConnectableEntry, type ConnectableTransport, ConnectorPicker, type ConnectorPickerClient, type ConnectorPickerLens, type ConnectorPickerProps, type ContextOrgGroup, ContextSwitcher, type ContextSwitcherItem, type ContextSwitcherProps, type FallbackModuleDef, Flyout, type FlyoutChild, type FlyoutProps, GLYPH_CANVAS_FILL, type GroupRungItem, type HeaderNavItem, LOCKUP_GAP, LOCKUP_ROW, MARK_CAP_RATIO, MARK_WEIGHT, REEVE_ACCENT, ResponsiveHeader, type ResponsiveHeaderProps, type RungItem, type SidebarLadder, type SidebarLadderParsed, type SidebarRung, type UseLadderRungOptions, WORDMARK_WEIGHT, deriveFallbackLadder, faviconUrl, groupByOrg, reconcileLadder, resolveLadder, useLadderRung, useResponsivePlaceholder, zSidebarLadder };