@cosmoops/connect-sdk 0.9.0 → 0.15.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 (139) hide show
  1. package/CHANGELOG.md +497 -0
  2. package/README.md +111 -41
  3. package/dist/cjs/cli/doctor.js +3 -3
  4. package/dist/cjs/cli/init.js +3 -3
  5. package/dist/cjs/client/components/access-gate.js +20 -1
  6. package/dist/cjs/client/components/app-shell.js +75 -41
  7. package/dist/cjs/client/components/apps-grid.js +3 -2
  8. package/dist/cjs/client/components/ask-ai.js +20 -1
  9. package/dist/cjs/client/components/confirm-dialog.js +40 -0
  10. package/dist/cjs/client/components/cursor-pagination.js +22 -0
  11. package/dist/cjs/client/components/filter-tabs.js +24 -0
  12. package/dist/cjs/client/components/grievance-notice.js +30 -0
  13. package/dist/cjs/client/components/link-button.js +17 -10
  14. package/dist/cjs/client/components/logo.js +4 -3
  15. package/dist/cjs/client/components/nav.js +41 -0
  16. package/dist/cjs/client/components/plan-pricing.js +27 -0
  17. package/dist/cjs/client/components/plans.js +1 -1
  18. package/dist/cjs/client/components/platform-gate.js +48 -0
  19. package/dist/cjs/client/components/shell-context.js +149 -0
  20. package/dist/cjs/client/components/side-nav.js +49 -23
  21. package/dist/cjs/client/components/sidebar.js +127 -0
  22. package/dist/cjs/client/components/subscribe-dialog.js +2 -2
  23. package/dist/cjs/client/components/subscription.js +1 -1
  24. package/dist/cjs/client/components/user-menu.js +96 -37
  25. package/dist/cjs/client/components/workspace-switcher.js +100 -0
  26. package/dist/cjs/client/index.js +69 -2
  27. package/dist/cjs/client/provider.js +57 -2
  28. package/dist/cjs/client/use-checkout.js +9 -5
  29. package/dist/cjs/errors.js +1 -1
  30. package/dist/cjs/index.js +11 -1
  31. package/dist/cjs/next/index.js +2 -2
  32. package/dist/cjs/server/client.js +25 -2
  33. package/dist/cjs/server/handler.js +196 -18
  34. package/dist/cjs/server/http.js +6 -2
  35. package/dist/cjs/server/resources/acting-as.js +16 -0
  36. package/dist/cjs/server/resources/ai.js +15 -2
  37. package/dist/cjs/server/resources/data.js +374 -0
  38. package/dist/cjs/server/resources/identity.js +7 -3
  39. package/dist/cjs/server/resources/kv.js +84 -0
  40. package/dist/cjs/server/resources/platform.js +43 -0
  41. package/dist/cjs/server/resources/sessions.js +1 -1
  42. package/dist/cjs/server/resources/workspaces.js +74 -0
  43. package/dist/cjs/store.js +152 -0
  44. package/dist/cjs/ui/empty-state.js +28 -0
  45. package/dist/cjs/ui/index.js +44 -0
  46. package/dist/cjs/ui/page-header.js +27 -0
  47. package/dist/cjs/ui/page-shell.js +44 -0
  48. package/dist/cjs/ui/section.js +20 -0
  49. package/dist/cjs/ui/setting-row.js +14 -0
  50. package/dist/cjs/ui/status-chip.js +17 -0
  51. package/dist/esm/cli/doctor.js +3 -3
  52. package/dist/esm/cli/init.js +3 -3
  53. package/dist/esm/client/components/access-gate.js +20 -1
  54. package/dist/esm/client/components/app-shell.js +81 -47
  55. package/dist/esm/client/components/apps-grid.js +3 -2
  56. package/dist/esm/client/components/ask-ai.js +20 -1
  57. package/dist/esm/client/components/confirm-dialog.js +37 -0
  58. package/dist/esm/client/components/cursor-pagination.js +19 -0
  59. package/dist/esm/client/components/filter-tabs.js +21 -0
  60. package/dist/esm/client/components/grievance-notice.js +27 -0
  61. package/dist/esm/client/components/link-button.js +17 -10
  62. package/dist/esm/client/components/logo.js +4 -3
  63. package/dist/esm/client/components/nav.js +40 -0
  64. package/dist/esm/client/components/plan-pricing.js +25 -0
  65. package/dist/esm/client/components/plans.js +2 -2
  66. package/dist/esm/client/components/platform-gate.js +45 -0
  67. package/dist/esm/client/components/shell-context.js +141 -0
  68. package/dist/esm/client/components/side-nav.js +48 -21
  69. package/dist/esm/client/components/sidebar.js +120 -0
  70. package/dist/esm/client/components/subscribe-dialog.js +3 -3
  71. package/dist/esm/client/components/subscription.js +2 -2
  72. package/dist/esm/client/components/user-menu.js +95 -38
  73. package/dist/esm/client/components/workspace-switcher.js +97 -0
  74. package/dist/esm/client/index.js +46 -4
  75. package/dist/esm/client/provider.js +53 -2
  76. package/dist/esm/client/use-checkout.js +9 -5
  77. package/dist/esm/errors.js +1 -1
  78. package/dist/esm/index.js +1 -0
  79. package/dist/esm/next/index.js +2 -2
  80. package/dist/esm/server/client.js +25 -2
  81. package/dist/esm/server/handler.js +196 -18
  82. package/dist/esm/server/http.js +6 -2
  83. package/dist/esm/server/resources/acting-as.js +13 -0
  84. package/dist/esm/server/resources/ai.js +15 -2
  85. package/dist/esm/server/resources/data.js +369 -0
  86. package/dist/esm/server/resources/identity.js +7 -3
  87. package/dist/esm/server/resources/kv.js +80 -0
  88. package/dist/esm/server/resources/platform.js +39 -0
  89. package/dist/esm/server/resources/sessions.js +1 -1
  90. package/dist/esm/server/resources/workspaces.js +70 -0
  91. package/dist/esm/store.js +141 -0
  92. package/dist/esm/ui/empty-state.js +25 -0
  93. package/dist/esm/ui/index.js +34 -0
  94. package/dist/esm/ui/page-header.js +24 -0
  95. package/dist/esm/ui/page-shell.js +41 -0
  96. package/dist/esm/ui/section.js +17 -0
  97. package/dist/esm/ui/setting-row.js +11 -0
  98. package/dist/esm/ui/status-chip.js +14 -0
  99. package/dist/types/client/components/app-shell.d.ts +65 -27
  100. package/dist/types/client/components/apps-grid.d.ts +11 -2
  101. package/dist/types/client/components/confirm-dialog.d.ts +34 -0
  102. package/dist/types/client/components/cursor-pagination.d.ts +27 -0
  103. package/dist/types/client/components/filter-tabs.d.ts +26 -0
  104. package/dist/types/client/components/grievance-notice.d.ts +31 -0
  105. package/dist/types/client/components/link-button.d.ts +32 -9
  106. package/dist/types/client/components/logo.d.ts +18 -2
  107. package/dist/types/client/components/nav.d.ts +24 -0
  108. package/dist/types/client/components/plan-pricing.d.ts +10 -0
  109. package/dist/types/client/components/platform-gate.d.ts +53 -0
  110. package/dist/types/client/components/shell-context.d.ts +116 -0
  111. package/dist/types/client/components/side-nav.d.ts +27 -13
  112. package/dist/types/client/components/sidebar.d.ts +95 -0
  113. package/dist/types/client/components/user-menu.d.ts +68 -22
  114. package/dist/types/client/components/workspace-switcher.d.ts +49 -0
  115. package/dist/types/client/index.d.ts +27 -5
  116. package/dist/types/client/provider.d.ts +109 -6
  117. package/dist/types/client/use-checkout.d.ts +8 -4
  118. package/dist/types/errors.d.ts +1 -1
  119. package/dist/types/index.d.ts +2 -0
  120. package/dist/types/next/index.d.ts +1 -1
  121. package/dist/types/server/client.d.ts +25 -2
  122. package/dist/types/server/http.d.ts +8 -2
  123. package/dist/types/server/index.d.ts +4 -0
  124. package/dist/types/server/resources/acting-as.d.ts +11 -0
  125. package/dist/types/server/resources/ai.d.ts +9 -1
  126. package/dist/types/server/resources/data.d.ts +258 -0
  127. package/dist/types/server/resources/kv.d.ts +63 -0
  128. package/dist/types/server/resources/platform.d.ts +32 -0
  129. package/dist/types/server/resources/workspaces.d.ts +55 -0
  130. package/dist/types/store.d.ts +96 -0
  131. package/dist/types/types.d.ts +319 -15
  132. package/dist/types/ui/empty-state.d.ts +34 -0
  133. package/dist/types/ui/index.d.ts +40 -0
  134. package/dist/types/ui/page-header.d.ts +34 -0
  135. package/dist/types/ui/page-shell.d.ts +39 -0
  136. package/dist/types/ui/section.d.ts +26 -0
  137. package/dist/types/ui/setting-row.d.ts +18 -0
  138. package/dist/types/ui/status-chip.d.ts +36 -0
  139. package/package.json +6 -2
@@ -1,20 +1,43 @@
1
1
  import { type ButtonProps } from "@heroui/react";
2
+ import type { ReactElement, ReactNode } from "react";
2
3
  export type ConnectLinkButtonProps = Omit<ButtonProps, "render"> & {
3
4
  href: string;
5
+ /**
6
+ * The host's router link, if it has one.
7
+ *
8
+ * Without it this renders a plain `<a href>`, which navigates under every
9
+ * router but costs a **full document load** - in an App Router app that
10
+ * throws away the client cache and re-runs the whole tree, which is very
11
+ * visible on a button sitting inside a page the user is working in.
12
+ *
13
+ * ```tsx
14
+ * <ConnectLinkButton href="/deals" renderLink={(props) => <Link {...props} />}>
15
+ * Open board
16
+ * </ConnectLinkButton>
17
+ * ```
18
+ *
19
+ * Same shape as `AppShell`'s `renderLink`, so a host declares its router
20
+ * once and passes the same function to both. This package imports nothing
21
+ * from `next` - see the `./next` entry's doc comment for why that is a hard
22
+ * rule - so handing the link component in is the only way it can be a client
23
+ * transition.
24
+ */
25
+ renderLink?: (props: {
26
+ href: string;
27
+ className?: string;
28
+ children: ReactNode;
29
+ }) => ReactElement;
4
30
  };
5
31
  /**
6
- * A HeroUI `Button` that renders a real `<a>`.
32
+ * A HeroUI `Button` that renders a link.
7
33
  *
8
- * A plain anchor, deliberately not a router link: this package imports nothing
9
- * from `next` (see the `./next` entry's doc comment for why that is a hard
10
- * rule), so it cannot know which router - if any - the host app uses. An
11
- * `<a href>` navigates under all of them, and a host that wants client-side
12
- * routing can pass its own element via `render` on the underlying `Button`.
34
+ * HeroUI v3's `Button` has no `as` prop, so `<Button as={Link}>` does not type
35
+ * check and does not work - this is the supported route, via `render`.
13
36
  *
14
37
  * The cast is the same one HeroUI's own "Render Function" example needs:
15
38
  * `render` types its argument as the props of the element `Button` would have
16
- * emitted, which is a `<button>` - including a `ref` typed to
39
+ * emitted, which is a `<button>`, including a `ref` typed to
17
40
  * `HTMLButtonElement`. Spreading that onto an anchor is sound at runtime and
18
- * unrepresentable in the type, which is exactly what a cast is for.
41
+ * unrepresentable in the type, which is what the cast is for.
19
42
  */
20
- export declare function ConnectLinkButton({ href, ...props }: Readonly<ConnectLinkButtonProps>): import("react").JSX.Element;
43
+ export declare function ConnectLinkButton({ href, renderLink, ...props }: Readonly<ConnectLinkButtonProps>): import("react").JSX.Element;
@@ -14,7 +14,23 @@ export interface ConnectLogoProps {
14
14
  * Falls back to the Connect rings when omitted.
15
15
  */
16
16
  icon?: ReactNode;
17
- /** Mark only, no wordmark - for a collapsed sidebar or a favicon-sized slot. */
17
+ /**
18
+ * Which halves of the lockup to draw.
19
+ *
20
+ * - `"full"` - badge and wordmark, the default.
21
+ * - `"mark"` - the badge alone, for a collapsed rail or a favicon-sized slot.
22
+ * - `"wordmark"` - the words alone, for a layout that already draws the
23
+ * badge somewhere fixed. The sidebar header uses this: the badge stays put
24
+ * while the words fade, and rendering a second badge only to hide it with
25
+ * a CSS child selector is how that kind of layout quietly rots.
26
+ */
27
+ variant?: "full" | "mark" | "wordmark";
28
+ /**
29
+ * Mark only, no wordmark.
30
+ *
31
+ * @deprecated Pass `variant="mark"`. Kept because it is the published 0.11
32
+ * API; `variant` wins when both are given.
33
+ */
18
34
  collapsed?: boolean;
19
35
  className?: string;
20
36
  }
@@ -32,4 +48,4 @@ export interface ConnectLogoProps {
32
48
  * as `text-sm`, with the badge, gap and tracking all in proportion. That is
33
49
  * why there is no `size` prop.
34
50
  */
35
- export declare function ConnectLogo({ name, icon, collapsed, className }: ConnectLogoProps): import("react").JSX.Element;
51
+ export declare function ConnectLogo({ name, icon, variant, collapsed, className }: ConnectLogoProps): import("react").JSX.Element;
@@ -33,6 +33,21 @@ export interface ConnectNavItem {
33
33
  * step with this one.
34
34
  */
35
35
  gateExempt?: boolean;
36
+ /**
37
+ * Who this route is for. Omit - the default - and it renders for everyone,
38
+ * which is what every route on an ordinary application is.
39
+ *
40
+ * - `"staff"` shows it only to a Connect platform staff session; your
41
+ * operator console goes here.
42
+ * - `"member"` hides it *from* staff. Reach for it on anything that reads
43
+ * the workspace - billing, plans, usage, team - since a staff session has
44
+ * no workspace and those routes can only fail for it.
45
+ *
46
+ * Filtering here is presentation, not access: it decides which links a
47
+ * person sees, and a link is not a permission. The route itself must still
48
+ * check `staff` on your server - see `<ConnectPlatformGate>`.
49
+ */
50
+ audience?: "staff" | "member";
36
51
  }
37
52
  /** A titled group of items. Untitled groups render as a plain divider-free block. */
38
53
  export interface ConnectNavSection {
@@ -106,5 +121,14 @@ export declare function resolveActivePath(pathname: string, nav: ConnectNav): st
106
121
  * lights up on every app's home route. External items are never active.
107
122
  */
108
123
  export declare function isNavItemActive(item: ConnectNavItem, activeHref: string | null): boolean;
124
+ /**
125
+ * `nav` with every item the current session should not see removed - see
126
+ * `ConnectNavItem.audience`.
127
+ *
128
+ * Applied to nested `items` too, and a parent whose children all disappear is
129
+ * dropped along with them: a section header pointing at nothing reads as a
130
+ * broken link rather than as a hidden one.
131
+ */
132
+ export declare function filterNavForAudience(nav: ConnectNav, isStaff: boolean): ConnectNav;
109
133
  /** Hrefs declared `gateExempt` - the routes `AppShell` renders outside its `ConnectAccessGate`. */
110
134
  export declare function gateExemptPaths(nav: ConnectNav): string[];
@@ -24,6 +24,16 @@ export declare function formatPrice(paise: number): string;
24
24
  * plan reads as an upgrade over every monthly one.
25
25
  */
26
26
  export declare function monthlyOf(price: number, billingInterval: Plan["billingInterval"]): number;
27
+ export declare function intervalLabel(billingInterval: Plan["billingInterval"]): {
28
+ per: string;
29
+ short: string;
30
+ adverb: string;
31
+ };
32
+ /**
33
+ * `/month`, `/year` - and nothing at all for a one-time plan, where a price
34
+ * with no slash is the honest rendering.
35
+ */
36
+ export declare function pricePerLabel(billingInterval: Plan["billingInterval"]): string;
27
37
  export declare function discountLabel(discount: AppliedDiscount): string;
28
38
  /**
29
39
  * The minimum a plan-like object has to expose to be grouped or compared -
@@ -0,0 +1,53 @@
1
+ import type { ReactNode } from "react";
2
+ export interface ConnectPlatformGateProps {
3
+ /**
4
+ * Restrict to these platform roles. Omit to accept any staff session, which
5
+ * is the common case - your application already chose which roles it accepts
6
+ * when a platform admin turned staff access on for it, and repeating that
7
+ * list here is a second copy to keep in sync.
8
+ *
9
+ * Pass one only when your own console has tiers: a `developer` seeing the
10
+ * read-only half of what a `platform_admin` can edit.
11
+ */
12
+ roles?: string[];
13
+ children: ReactNode;
14
+ /**
15
+ * Rendered for an ordinary member session. Defaults to `null` - hiding the
16
+ * subtree, which is what this gate is normally for. An operator console is
17
+ * usually better hidden than explained.
18
+ */
19
+ fallback?: ReactNode;
20
+ /** Shown until the session read settles. Defaults to nothing. */
21
+ loading?: ReactNode;
22
+ }
23
+ /**
24
+ * Renders `children` only for a Connect platform staff session.
25
+ *
26
+ * ```tsx
27
+ * <ConnectPlatformGate>
28
+ * <LinkMasterConsole />
29
+ * </ConnectPlatformGate>
30
+ * ```
31
+ *
32
+ * Staff are Connect's own operators - `platform_admin`, `platform_manager`,
33
+ * `developer` - signing into *your* application. They belong to no workspace,
34
+ * so a staff session carries no `workspaceId`, no subscription and no member
35
+ * role; see `SessionAccess.staff`. Staff access is off by default and turned on
36
+ * per application by a Connect platform admin, so this gate renders nothing
37
+ * until that happens.
38
+ *
39
+ * **This is a UX gate, not a security boundary** - the same caveat every gate
40
+ * in this package carries, and it matters more here than anywhere else,
41
+ * because what is behind this one is usually an operator console. The browser
42
+ * can ignore this component entirely. Every route it reveals must re-check
43
+ * `staff` on your own server:
44
+ *
45
+ * ```ts
46
+ * const { staff, role } = await client.identity.getUser(sessionToken);
47
+ * if (!staff) return forbidden();
48
+ * ```
49
+ *
50
+ * Renders `loading` - not `fallback` - while the session is unknown, so an
51
+ * operator never flickers through the customer layout on the way in.
52
+ */
53
+ export declare function ConnectPlatformGate({ roles, children, fallback, loading, }: ConnectPlatformGateProps): import("react").JSX.Element;
@@ -0,0 +1,116 @@
1
+ import { type ReactNode } from "react";
2
+ import type { ConnectLinkRenderer } from "./nav.js";
3
+ /** A plain anchor - correct anywhere, and a full page load. See `ConnectLinkRenderer`. */
4
+ export declare const defaultRenderLink: ConnectLinkRenderer;
5
+ /**
6
+ * The rail's two widths, in pixels, and the one place they are written down.
7
+ *
8
+ * They are numbers rather than Tailwind classes because the rail animates
9
+ * between them - `motion` interpolates a number and cannot interpolate
10
+ * `w-16` into `w-64`.
11
+ *
12
+ * **The pairing is load-bearing, not arbitrary.** Every row in the sidebar is
13
+ * laid out so its glyph sits exactly `RAIL_GUTTER + ROW_GUTTER + 10px` from
14
+ * the rail's left edge - 32px, which is also `RAIL_COLLAPSED / 2`. So the
15
+ * collapsed rail centres every icon *without a single centring rule*, and
16
+ * nothing moves horizontally while the width animates. Change either width
17
+ * and that identity breaks: the icons drift sideways during the transition,
18
+ * which is the exact stutter this geometry exists to avoid.
19
+ */
20
+ export declare const RAIL_COLLAPSED = 64;
21
+ export declare const RAIL_EXPANDED = 264;
22
+ /** Horizontal padding inside the rail, on every region. Tailwind `px-3`. */
23
+ export declare const RAIL_GUTTER = 12;
24
+ /**
25
+ * The row shape every sidebar control shares - nav items, the workspace tile,
26
+ * the launcher, the user button.
27
+ *
28
+ * `overflow-hidden` + `whitespace-nowrap` is what makes the collapse smooth:
29
+ * the label is *clipped* by the shrinking rail rather than re-wrapped or
30
+ * re-measured, so a width animation costs no reflow. Fading the label with
31
+ * `SidebarLabel` on top of that hides the clip itself.
32
+ */
33
+ export declare const SIDEBAR_ROW = "group/row relative flex h-10 w-full shrink-0 items-center justify-start gap-3 overflow-hidden whitespace-nowrap rounded-lg pe-2 text-sm font-normal transition-colors focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-focus";
34
+ /**
35
+ * Leading inset for a row whose glyph is 20px wide (a nav icon, the launcher).
36
+ * `10 + 10 = 20`, and `RAIL_GUTTER + 20 = 32`. See `RAIL_COLLAPSED`.
37
+ */
38
+ export declare const SIDEBAR_ROW_ICON = "ps-[10px]";
39
+ /**
40
+ * Leading inset for a row whose glyph is 32px wide (the avatar, the workspace
41
+ * tile). `4 + 16 = 20`, landing on the same 32px centre as `SIDEBAR_ROW_ICON`.
42
+ */
43
+ export declare const SIDEBAR_ROW_TILE = "ps-[4px]";
44
+ /**
45
+ * The class list for one sidebar row, in one place.
46
+ *
47
+ * Three different elements need it - a plain `<button>` (`ConnectSidebarRow`),
48
+ * a rendered link (`ConnectSidebarNav`), and a HeroUI `<Button>` acting as a
49
+ * `Modal`/`Dropdown` trigger (the account row, the launcher, the workspace
50
+ * tile). They cannot share a component, because a React Aria overlay has to
51
+ * own its own trigger element - so they share this instead. A second copy of
52
+ * the string is a row that drifts off the 32px centre and takes the collapse
53
+ * animation's smoothness with it.
54
+ *
55
+ * On the HeroUI trigger: these are all Tailwind *utilities*, and HeroUI's
56
+ * `.button` is a component class, so the utility layer wins - which is what
57
+ * lets a `w-fit`, centred, `px-`-padded button take this shape at all.
58
+ */
59
+ export declare function sidebarRowClass(options?: Readonly<{
60
+ /** The glyph is 32px (an avatar, a workspace tile) rather than 20px. */
61
+ tile?: boolean;
62
+ /** Paint the active treatment. */
63
+ active?: boolean;
64
+ className?: string;
65
+ }>): string;
66
+ export interface ConnectShellContextValue {
67
+ /** Icon-only rail. Always `false` inside the mobile drawer - see `AppShell`. */
68
+ collapsed: boolean;
69
+ /** Whether a collapse control should be offered at all. */
70
+ collapsible: boolean;
71
+ setCollapsed: (collapsed: boolean) => void;
72
+ renderLink: ConnectLinkRenderer;
73
+ /** The host router's current path, if it supplied one. */
74
+ activePath?: string;
75
+ /** Called after any sidebar link is pressed - the mobile drawer closes itself with this. */
76
+ onNavigate?: () => void;
77
+ }
78
+ /**
79
+ * Shared state for every part of the shell, so `ConnectSidebarNav`,
80
+ * `ConnectWorkspaceSwitcher` and `ConnectUserButton` all know whether the rail
81
+ * is collapsed without the host threading a prop through three layers.
82
+ *
83
+ * This is the piece that makes the shell *composable* rather than only
84
+ * configurable: the parts are exported and can be rearranged, and they still
85
+ * agree about collapse, routing and the mobile drawer because they read one
86
+ * context instead of one another's props.
87
+ */
88
+ export declare function ConnectShellProvider({ value, children, }: Readonly<{
89
+ value: ConnectShellContextValue;
90
+ children: ReactNode;
91
+ }>): import("react").JSX.Element;
92
+ /**
93
+ * The shell state a sidebar part is rendering inside.
94
+ *
95
+ * Falls back to "expanded, not collapsible, plain anchors" rather than
96
+ * throwing, because every part is also exported for use *outside* a shell -
97
+ * a host dropping `ConnectSidebarNav` into a layout it already owns should
98
+ * not have to mount a provider to get a nav column.
99
+ */
100
+ export declare function useConnectShell(): ConnectShellContextValue;
101
+ /**
102
+ * A sidebar row's text, faded out as the rail collapses.
103
+ *
104
+ * Opacity only - never width, height or margin. The rail's own width
105
+ * animation already moves the clip edge past this text; animating the text's
106
+ * box as well would make two things fight over the same pixels and is what
107
+ * makes a collapsing sidebar look like it stutters.
108
+ *
109
+ * Asymmetric timing on purpose. Collapsing, the label leaves *first* (no
110
+ * delay, quick) so the rail never appears to squeeze text; expanding, it
111
+ * arrives *after* the rail has opened enough to hold it.
112
+ */
113
+ export declare function SidebarLabel({ children, className, }: Readonly<{
114
+ children: ReactNode;
115
+ className?: string;
116
+ }>): import("react").JSX.Element;
@@ -1,19 +1,20 @@
1
+ import { defaultRenderLink } from "./shell-context.js";
1
2
  import { type ConnectLinkRenderer, type ConnectNav } from "./nav.js";
2
- /** A plain anchor - correct anywhere, and a full page load. See `ConnectLinkRenderer`. */
3
- export declare const defaultRenderLink: ConnectLinkRenderer;
4
- export interface ConnectSideNavProps {
3
+ export { defaultRenderLink };
4
+ export interface ConnectSidebarNavProps {
5
5
  /** Items, sections, or a mix - see `ConnectNav`. */
6
6
  nav: ConnectNav;
7
7
  /**
8
- * The current path. Pass your router's own (`usePathname()` in Next) -
9
- * without it the nav reads `window.location` and patches `history` to hear
10
- * about client-side navigations, which works but is guesswork the router
11
- * never has to do. See `useActivePath`.
8
+ * The current path. Pass your router's own (`usePathname()` in Next).
9
+ * Inherited from `AppShell` when omitted; without either, the nav
10
+ * reads `window.location` and patches `history` to hear about client-side
11
+ * navigations - which works, but is guesswork the router never has to do.
12
+ * See `useActivePath`.
12
13
  */
13
14
  activePath?: string;
14
- /** See `ConnectLinkRenderer`. Defaults to a plain `<a>`. */
15
+ /** See `ConnectLinkRenderer`. Inherited from the shell; defaults to a plain `<a>`. */
15
16
  renderLink?: ConnectLinkRenderer;
16
- /** Icon-only rail. Labels move into tooltips and children are hidden. */
17
+ /** Icon-only rail. Inherited from the shell when omitted. */
17
18
  collapsed?: boolean;
18
19
  /** Called after any nav link is pressed - the mobile drawer closes itself with this. */
19
20
  onNavigate?: () => void;
@@ -24,8 +25,21 @@ export interface ConnectSideNavProps {
24
25
  * children, and the active highlight.
25
26
  *
26
27
  * `AppShell` renders this inside its rail and its mobile drawer, and it
27
- * is exported for a host that wants the shell's navigation without its
28
- * chrome: the same items, the same active resolution, dropped into a layout
29
- * you already have.
28
+ * is exported for a host that wants the shell's navigation without its chrome:
29
+ * the same items, the same active resolution, dropped into a layout you
30
+ * already have.
31
+ *
32
+ * Every prop is optional except `nav`, because inside a shell they are all
33
+ * inherited from it. Passing one wins over what the shell says - which is how
34
+ * a second nav column (see `secondaryNav`) can share the rail's collapse state
35
+ * while keeping its own items.
36
+ */
37
+ export declare function ConnectSidebarNav({ nav, activePath, renderLink, collapsed, onNavigate, className, }: ConnectSidebarNavProps): import("react").JSX.Element;
38
+ /**
39
+ * @deprecated Renamed to `ConnectSidebarNav` in 0.12 - the rail is a
40
+ * `ConnectSidebar` now, and its pieces are named after it. Identical
41
+ * behaviour; this alias will be removed in a later minor.
30
42
  */
31
- export declare function ConnectSideNav({ nav, activePath, renderLink, collapsed, onNavigate, className, }: ConnectSideNavProps): import("react").JSX.Element;
43
+ export declare const ConnectSideNav: typeof ConnectSidebarNav;
44
+ /** @deprecated Renamed to `ConnectSidebarNavProps`. */
45
+ export type ConnectSideNavProps = ConnectSidebarNavProps;
@@ -0,0 +1,95 @@
1
+ import type { ComponentPropsWithoutRef, ReactNode } from "react";
2
+ export interface ConnectSidebarProps {
3
+ /**
4
+ * The rail's contents, top to bottom. `AppShell` composes the default
5
+ * set; pass your own to rearrange them.
6
+ */
7
+ children: ReactNode;
8
+ /** Accessible name for the `<aside>`. Defaults to `"Sidebar"`. */
9
+ label?: string;
10
+ className?: string;
11
+ }
12
+ /**
13
+ * The application rail: a fixed-width column that animates between an
14
+ * icon-only 64px and a labelled 264px.
15
+ *
16
+ * **Why width, and nothing else, animates.** Every row inside puts its glyph
17
+ * on the same 32px centre in both states (see `RAIL_COLLAPSED`), and every
18
+ * label is clipped rather than re-measured (see `SIDEBAR_ROW`). So the only
19
+ * property in flight is this element's width: no child moves, no text
20
+ * re-wraps, and there is no per-frame layout pass on the rows. That is why the
21
+ * collapse looks smooth rather than merely fast.
22
+ *
23
+ * Hidden below `md`. The mobile drawer renders the same children with
24
+ * `collapsed: false`, because a 64px icon rail on a phone is a worse drawer,
25
+ * not a smaller one.
26
+ */
27
+ export declare function ConnectSidebar({ children, label, className }: ConnectSidebarProps): import("react").JSX.Element;
28
+ export interface ConnectSidebarHeaderProps {
29
+ /** This application's name, in the `cosmo·ops VOICE` lockup. See `ConnectLogo`. */
30
+ name: string;
31
+ /** This application's glyph, drawn on the CosmoOps badge. */
32
+ icon?: ReactNode;
33
+ /** Where the lockup links when the rail is expanded. Defaults to `/`. */
34
+ homeHref?: string;
35
+ }
36
+ /**
37
+ * The brand row, and the rail's own expand/collapse control.
38
+ *
39
+ * **Two states, one DOM.** The badge is mounted once and never re-created; the
40
+ * wordmark fades beside it and the collapse button fades at the trailing edge.
41
+ * Swapping which *element* renders the brand would remount it halfway through
42
+ * the width animation and flicker.
43
+ *
44
+ * Collapsed, hovering or focusing the badge swaps it for the expand glyph, so
45
+ * the rail needs no permanent control eating a row. Expanded, the control sits
46
+ * at the header's trailing edge, where it can be aimed at without covering the
47
+ * brand. The swap is pure CSS on a `group` - hovering re-renders nothing,
48
+ * which is what keeps it instant.
49
+ *
50
+ * The trade this makes, deliberately: **collapsed, the lockup is no longer a
51
+ * link home.** It has become the expand control, and one target that navigates
52
+ * on click but expands on click-after-hover would be worse than either. Put
53
+ * home in the nav; every application already does.
54
+ */
55
+ export declare function ConnectSidebarHeader({ name, icon, homeHref }: ConnectSidebarHeaderProps): import("react").JSX.Element;
56
+ /**
57
+ * The rail's scrolling region, for the primary navigation. Everything else in
58
+ * the sidebar is `shrink-0`, so this is the only part that ever gives way.
59
+ */
60
+ export declare function ConnectSidebarBody({ children }: Readonly<{
61
+ children: ReactNode;
62
+ }>): import("react").JSX.Element;
63
+ /**
64
+ * A pinned region at the foot of the rail - the secondary nav, the launcher,
65
+ * the account row.
66
+ *
67
+ * `separated` draws the hairline above it. The account row wants one; a
68
+ * secondary nav sitting directly on top of the account row does not, or the
69
+ * rail ends in two rules a few pixels apart.
70
+ */
71
+ export declare function ConnectSidebarFooter({ children, separated, }: Readonly<{
72
+ children: ReactNode;
73
+ separated?: boolean;
74
+ }>): import("react").JSX.Element;
75
+ export type ConnectSidebarRowProps = Readonly<{
76
+ /** The glyph. 20px by default; 32px when `tile`. */
77
+ icon: ReactNode;
78
+ /** The row's text, and - when it is a string - its accessible name. */
79
+ label: ReactNode;
80
+ /** Overrides the accessible name. Required when `label` is not a string. */
81
+ "aria-label"?: string;
82
+ /** The glyph is 32px (an avatar, a workspace tile) rather than 20px. */
83
+ tile?: boolean;
84
+ /** Trailing content, e.g. a chevron. Clipped with the label while collapsed. */
85
+ trailing?: ReactNode;
86
+ }> & Omit<ComponentPropsWithoutRef<"button">, "aria-label">;
87
+ /**
88
+ * A pressable row in the rail that is not a nav item - the launcher, the
89
+ * account button, anything a host adds to the footer.
90
+ *
91
+ * Exported so a host's own row lines up with the SDK's. It applies the same
92
+ * 32px glyph centre and the same label fade, which is what stops a partner's
93
+ * "Help" row drifting sideways while every row above it holds still.
94
+ */
95
+ export declare function ConnectSidebarRow({ icon, label, tile, trailing, className, "aria-label": ariaLabel, ...props }: ConnectSidebarRowProps): import("react").JSX.Element;
@@ -14,35 +14,81 @@ export interface ConnectUserMenuProps {
14
14
  theme?: ThemeMode;
15
15
  onThemeChange?: (theme: ThemeMode) => void;
16
16
  }
17
+ export interface ConnectProfileModalProps {
18
+ /** Controlled open state. */
19
+ isOpen: boolean;
20
+ onOpenChange: (isOpen: boolean) => void;
21
+ /** Called after sign-out completes, e.g. to route home. */
22
+ onSignOut?: () => void;
23
+ /** See `ConnectUserButtonProps.theme`. */
24
+ theme?: ThemeMode;
25
+ onThemeChange?: (theme: ThemeMode) => void;
26
+ }
17
27
  /**
18
- * Avatar and name in the trigger; an identity card, the three Connect
19
- * destinations (Profile, Billing, Connect), an optional appearance control,
20
- * this account's active sessions, and sign-out in the modal it opens.
28
+ * The account modal with **no trigger of its own**, driven by the host.
29
+ *
30
+ * For an application that already has an account control it wants to keep -
31
+ * an avatar in its own chrome, a "Manage account" row in its own settings
32
+ * page - and wants Connect's modal behind it rather than rebuilding identity,
33
+ * sessions and sign-out. `ConnectUserButton` is the same modal with the
34
+ * trigger supplied; reach for this one only when that trigger is in the way.
21
35
  *
22
- * Profile/Billing/Connect open in a **new tab**, each marked with an
23
- * external-link icon. They are a different application at a different
24
- * origin, and navigating the host app away to reach them would lose
25
- * whatever the user was doing here. Sign out is the only action that acts
26
- * in place, which is why it sits alone in the footer rather than as a fourth
27
- * item in the same list as three navigations.
36
+ * Renders nothing when signed out, open or not - there is no account to show.
37
+ */
38
+ export declare function ConnectProfileModal({ isOpen, onOpenChange, onSignOut, theme, onThemeChange, }: ConnectProfileModalProps): import("react").JSX.Element | null;
39
+ export interface ConnectUserButtonProps {
40
+ /**
41
+ * How the trigger is drawn.
42
+ *
43
+ * - `"row"` - a full-width sidebar row: avatar, name over email, and a
44
+ * trailing glyph. The default, because the account control now lives in
45
+ * the rail's footer rather than in a top bar.
46
+ * - `"compact"` - the avatar alone, for a dense strip.
47
+ * - `"inline"` - avatar and name side by side, for a bar with room.
48
+ */
49
+ variant?: "row" | "compact" | "inline";
50
+ /** Called after sign-out completes, e.g. to route home. */
51
+ onSignOut?: () => void;
52
+ className?: string;
53
+ /**
54
+ * Current theme, and where to send a change - the SDK has no theme provider
55
+ * of its own (no `next-themes` dependency), so this is entirely
56
+ * host-controlled. The Appearance section only renders when both are
57
+ * supplied; omit either to leave theme control out of the modal.
58
+ */
59
+ theme?: ThemeMode;
60
+ onThemeChange?: (theme: ThemeMode) => void;
61
+ }
62
+ /**
63
+ * The signed-in account, as a control that opens the account modal.
28
64
  *
29
- * The session list is account-wide, not scoped to this application - see
30
- * `ConnectUserSession`'s doc comment in `types.ts`. Appearance only renders
31
- * when both `theme` and `onThemeChange` are supplied.
65
+ * In `"row"` form this is the last thing in the sidebar, which is where an
66
+ * account control belongs: it is the one item in the rail that is about *you*
67
+ * rather than about the product, and the foot of a column is where every
68
+ * application a customer already uses puts that. It collapses with the rail -
69
+ * the avatar holds its position and the name fades - because the avatar alone
70
+ * is still a complete account control, which is exactly what an icon rail
71
+ * needs.
32
72
  *
33
- * Renders nothing when signed out - pair it with `<ConnectLoginButton>`, which
34
- * renders nothing when signed *in*, and the two swap cleanly with no branch in
35
- * your own layout.
73
+ * Renders nothing when signed out. Pair it with `<ConnectLoginButton>`, which
74
+ * renders nothing when signed *in*, and the two swap with no branch in your
75
+ * own layout.
36
76
  *
37
77
  * On the HeroUI shape: the trigger is a plain `<Button>` as a **direct child**
38
- * of `<Modal>`, not wrapped in `<Modal.Trigger>`. `Dropdown.Trigger` (and by
39
- * the same construction, `Modal.Trigger`) is itself a React Aria `Button`, so
40
- * wrapping one produces a button inside a button - invalid markup, and press
41
- * handling stops working. Kept uncontrolled (no `isOpen`/`onOpenChange`),
42
- * matching `<ConnectAppsGrid>`'s own `<Modal>` - nothing here needs to close
43
- * the modal programmatically; sign-out uses `slot="close"` instead.
78
+ * of `<Modal>`, not wrapped in `<Modal.Trigger>`. `Dropdown.Trigger` - and by
79
+ * the same construction `Modal.Trigger` - is itself a React Aria `Button`, so
80
+ * wrapping one produces a button inside a button: invalid markup, and press
81
+ * handling stops working.
82
+ */
83
+ export declare function ConnectUserButton({ variant, onSignOut, className, theme, onThemeChange, }: ConnectUserButtonProps): import("react").JSX.Element | null;
84
+ /**
85
+ * @deprecated Renamed to `ConnectUserButton` in 0.12 - it is one button that
86
+ * opens a modal, and calling it a "menu" had people looking for a dropdown
87
+ * that never existed. `compact` maps to `variant="compact"`, and the old
88
+ * default (avatar plus name) to `variant="inline"`. This alias will be removed
89
+ * in a later minor.
44
90
  */
45
- export declare function ConnectUserMenu({ compact, onSignOut, className, theme, onThemeChange, }: ConnectUserMenuProps): import("react").JSX.Element | null;
91
+ export declare function ConnectUserMenu({ compact, ...rest }: ConnectUserMenuProps): import("react").JSX.Element;
46
92
  /**
47
93
  * The "Manage subscription" action on its own, for layouts that want it
48
94
  * visible rather than buried in the account menu - a billing banner, an empty
@@ -0,0 +1,49 @@
1
+ import type { UserWorkspace } from "../../types.js";
2
+ export interface ConnectWorkspaceSwitcherProps {
3
+ /**
4
+ * Called after a successful switch. Defaults to a full reload, which is
5
+ * almost always right: every server-rendered page in the host application
6
+ * was built for the *previous* workspace, and a client-side refresh would
7
+ * leave stale data on screen next to the new workspace's name.
8
+ */
9
+ onSwitched?: (workspace: UserWorkspace) => void;
10
+ /**
11
+ * Draw the trigger as a sidebar row rather than a plain button. On by
12
+ * default inside `AppShell`, where this sits directly under the
13
+ * brand; pass `false` for a standalone placement in your own chrome.
14
+ */
15
+ row?: boolean;
16
+ className?: string;
17
+ }
18
+ /**
19
+ * Switches which workspace this application is looking at.
20
+ *
21
+ * **Rendered in the `AppShell` sidebar header by default**, directly
22
+ * under the brand - which workspace you are in scopes everything below it, so
23
+ * it reads as a heading for the nav rather than as one more destination.
24
+ *
25
+ * It decides for itself whether to appear: an account with one workspace is
26
+ * shown nothing, because a switcher offering one option teaches the user
27
+ * nothing and costs a row of chrome on every screen. That is the opposite of
28
+ * Connect's own switcher, which always renders - Connect is where a workspace
29
+ * is *created*, so hiding it at one workspace would guarantee nobody ever
30
+ * reached two. A partner application creates nothing, so it has no such duty.
31
+ *
32
+ * **Authorization is the data, not a prop.** The list comes from the caller's
33
+ * own session (`GET /workspaces` takes no user id), so a workspace the user
34
+ * may not reach is simply not in it. There is deliberately no `roles` prop to
35
+ * pass or forget - the only way to render a row is for Connect to have
36
+ * returned it.
37
+ *
38
+ * Rows the user *belongs to* but cannot use here - a workspace outside the
39
+ * application's audience, or one with no subscription - are shown **disabled
40
+ * with the reason** rather than hidden. Hiding them reads as data loss to the
41
+ * person who owns the workspace; `switch` refuses the same two cases, so the
42
+ * label and the outcome always agree.
43
+ *
44
+ * A switch rotates the session token. The bridge persists the new one in the
45
+ * host application's own session store and the token never reaches this
46
+ * component - so the only thing to do afterwards is reload, which is what
47
+ * `onSwitched` defaults to.
48
+ */
49
+ export declare function ConnectWorkspaceSwitcher({ onSwitched, row, className, }: Readonly<ConnectWorkspaceSwitcherProps>): import("react").JSX.Element | null;