@cosmoops/connect-sdk 0.25.0 → 0.27.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 (70) hide show
  1. package/CHANGELOG.md +144 -0
  2. package/README.md +21 -1
  3. package/dist/cjs/client/components/access-gate.js +31 -5
  4. package/dist/cjs/client/components/account-sessions.js +4 -3
  5. package/dist/cjs/client/components/app-shell.js +11 -1
  6. package/dist/cjs/client/components/booking-calendar.js +2 -1
  7. package/dist/cjs/client/components/cursor-pagination.js +3 -2
  8. package/dist/cjs/client/components/filter-tabs.js +23 -5
  9. package/dist/cjs/client/components/grievance-notice.js +2 -1
  10. package/dist/cjs/client/components/logo.js +3 -2
  11. package/dist/cjs/client/components/one-tap.js +2 -1
  12. package/dist/cjs/client/components/plans.js +3 -2
  13. package/dist/cjs/client/components/readiness.js +4 -3
  14. package/dist/cjs/client/components/shell-context.js +2 -1
  15. package/dist/cjs/client/components/side-nav.js +2 -1
  16. package/dist/cjs/client/components/sidebar.js +19 -2
  17. package/dist/cjs/client/components/subscription.js +5 -4
  18. package/dist/cjs/client/components/usage-meter.js +2 -1
  19. package/dist/cjs/client/components/user-menu.js +3 -2
  20. package/dist/cjs/cn.js +53 -0
  21. package/dist/cjs/server/client.js +1 -1
  22. package/dist/cjs/server/handler.js +15 -0
  23. package/dist/cjs/server/resources/scheduling.js +23 -0
  24. package/dist/cjs/store.js +14 -1
  25. package/dist/cjs/ui/empty-state.js +2 -1
  26. package/dist/cjs/ui/index.js +8 -1
  27. package/dist/cjs/ui/page-header.js +2 -1
  28. package/dist/cjs/ui/page-shell.js +3 -2
  29. package/dist/cjs/ui/section.js +2 -1
  30. package/dist/cjs/ui/setting-row.js +2 -1
  31. package/dist/esm/client/components/access-gate.js +33 -7
  32. package/dist/esm/client/components/account-sessions.js +4 -3
  33. package/dist/esm/client/components/app-shell.js +11 -1
  34. package/dist/esm/client/components/booking-calendar.js +2 -1
  35. package/dist/esm/client/components/cursor-pagination.js +3 -2
  36. package/dist/esm/client/components/filter-tabs.js +23 -5
  37. package/dist/esm/client/components/grievance-notice.js +2 -1
  38. package/dist/esm/client/components/logo.js +3 -2
  39. package/dist/esm/client/components/one-tap.js +2 -1
  40. package/dist/esm/client/components/plans.js +3 -2
  41. package/dist/esm/client/components/readiness.js +4 -3
  42. package/dist/esm/client/components/shell-context.js +2 -1
  43. package/dist/esm/client/components/side-nav.js +2 -1
  44. package/dist/esm/client/components/sidebar.js +19 -2
  45. package/dist/esm/client/components/subscription.js +5 -4
  46. package/dist/esm/client/components/usage-meter.js +2 -1
  47. package/dist/esm/client/components/user-menu.js +3 -2
  48. package/dist/esm/cn.js +48 -0
  49. package/dist/esm/server/client.js +1 -1
  50. package/dist/esm/server/handler.js +15 -0
  51. package/dist/esm/server/resources/scheduling.js +23 -0
  52. package/dist/esm/store.js +14 -1
  53. package/dist/esm/ui/empty-state.js +2 -1
  54. package/dist/esm/ui/index.js +6 -0
  55. package/dist/esm/ui/page-header.js +2 -1
  56. package/dist/esm/ui/page-shell.js +3 -2
  57. package/dist/esm/ui/section.js +2 -1
  58. package/dist/esm/ui/setting-row.js +2 -1
  59. package/dist/theme.css +53 -0
  60. package/dist/types/client/components/access-gate.d.ts +35 -4
  61. package/dist/types/client/components/cursor-pagination.d.ts +6 -1
  62. package/dist/types/client/components/filter-tabs.d.ts +9 -3
  63. package/dist/types/cn.d.ts +48 -0
  64. package/dist/types/server/client.d.ts +1 -1
  65. package/dist/types/server/resources/scheduling.d.ts +19 -0
  66. package/dist/types/store.d.ts +6 -0
  67. package/dist/types/types.d.ts +12 -0
  68. package/dist/types/ui/index.d.ts +6 -0
  69. package/dist/types/ui/page-shell.d.ts +7 -1
  70. package/package.json +1 -1
package/CHANGELOG.md CHANGED
@@ -3,6 +3,150 @@
3
3
  `@cosmoops/connect-sdk` follows semver, with the 0.x convention that **minor
4
4
  versions may carry breaking changes** and patch versions never do.
5
5
 
6
+ ## 0.27.0
7
+
8
+ A minor rather than a patch because `Booking` gains a required field, so a
9
+ hand-written object satisfying the old interface no longer type-checks. Nothing
10
+ you *read* from the API breaks.
11
+
12
+ ### Added
13
+
14
+ - **`connect.scheduling.getBooking()`** - one appointment, by the id you
15
+ stored.
16
+
17
+ ```ts
18
+ const booking = await connect.scheduling.getBooking({
19
+ workspaceId,
20
+ bookingId, // the id you kept on your own record
21
+ });
22
+ ```
23
+
24
+ This is the read to make when a record of yours - a meeting on a deal, an
25
+ appointment on a case - holds a booking id. **`listBookings` cannot serve
26
+ it.** That is a window over `startAt`, and the `startAt` you cached is
27
+ exactly what is wrong once somebody has moved the appointment on the
28
+ workspace's own calendar, so the window you build from it comes back without
29
+ the row - in the one case worth re-reading for.
30
+
31
+ A retired booking still resolves, deliberately. `status: "rescheduled"`
32
+ answers with `rescheduledToId`, so an id stored months ago leads to the
33
+ appointment that exists today; follow the pointer. `cancelled` answers with
34
+ its reason. `not_found` means this workspace never had that booking, which is
35
+ a different fact from "it moved".
36
+
37
+ `GET /scheduling/booking?bookingId=` is the matching browser bridge route -
38
+ singular, and the id in the query string, because the bridge's route table is
39
+ matched by exact string. Same reason `reschedule` and `cancel` take theirs in
40
+ the body.
41
+
42
+ - **`Booking.hostMemberIds`** - the workspace members committed to an
43
+ appointment, so an integration can render who it belongs to.
44
+
45
+ These are the `WorkspaceMember.id` values `workspaces.listMembers` returns,
46
+ so an owner column is a lookup against a roster you already hold rather than
47
+ another round trip. They were withheld as "Connect internals", which
48
+ `listBookings` had already contradicted by *filtering* on `hostMemberId` - a
49
+ partner could narrow a diary by a value it was never allowed to read back.
50
+
51
+ Resolved when the slot was held and frozen there: a round-robin event type
52
+ picks one host, a collective one commits the whole group, and re-pointing the
53
+ event type afterwards does not move an appointment already made.
54
+
55
+ **This is the breaking half.** The field is required, so code constructing a
56
+ `Booking` literal - a test fixture, a mock, a storybook arg - needs
57
+ `hostMemberIds: []` added. Code that only reads bookings from the API is
58
+ unaffected.
59
+
60
+ ## 0.26.1
61
+
62
+ ### Added
63
+
64
+ - **`<ConnectAppShell>` renders a skip link.** The rail is the shell's first
65
+ content, so a keyboard or switch user tabbed every navigation row, the app
66
+ launcher and the account menu again on **every** route change before reaching
67
+ the page they came for - WCAG 2.4.1, and the one bypass a single-rail layout
68
+ cannot do without, because there is no second landmark to jump between. The
69
+ link is the first focusable element in the document, visually hidden until it
70
+ takes focus, and `<main>` now carries `id="connect-main-content"` and
71
+ `tabIndex={-1}` so focus genuinely lands there. Without that `tabIndex` a
72
+ browser scrolls to the anchor and leaves focus where it was, so the next Tab
73
+ returns to the top of the rail and the link appears to do nothing.
74
+
75
+ Nothing to adopt: it is on for every platform. The only visible change is a
76
+ "Skip to content" button that appears at the top-left on the first Tab.
77
+
78
+ ## 0.26.0
79
+
80
+ A minor rather than a patch because two of these change how existing screens
81
+ *look*, without changing any API. 0.25.1 was never published; its notes below
82
+ ship as part of this release.
83
+
84
+ ### Changed
85
+
86
+ - **The shared theme now sets `--radius`, and partner platforms get smaller
87
+ corners.** It never carried the token, so every platform importing it fell
88
+ through to HeroUI's stock `--radius: 0.5rem` with `--field-radius` derived
89
+ at 1.5x - while Connect's own app has always run at `0.25rem`. Cards,
90
+ inputs, chips and buttons in a partner app were drawn at twice the radius of
91
+ the same component in Connect, which was the one place two products sharing
92
+ this palette visibly disagreed. HeroUI's whole radius scale is *ratios* of
93
+ that one token, so this is a single value and every corner follows it.
94
+ To keep the old look, redefine both after the import:
95
+ `:root { --radius: 0.5rem; --field-radius: calc(var(--radius) * 1.5); }`.
96
+
97
+ - **`<ConnectAppShell>`'s content area is full width.** It was capped at
98
+ `max-w-6xl` and centred, which spent the width twice: dead margin down both
99
+ sides, and a fitted layout - a board that folds a column away when it runs
100
+ out of room - holding the pixels that would have paid for another column.
101
+ Landing surfaces on an application platform are boards, tables and
102
+ dashboards, and all three want the monitor. Forms and prose should cap
103
+ themselves at the surface rather than have the shell cap everything; pass
104
+ `contentClassName` to restore a frame of your own.
105
+
106
+ ### Fixed
107
+
108
+ - **`className` now actually overrides.** Every component concatenated your
109
+ classes onto its defaults - `` `flex flex-col gap-4 ${className}` `` - which
110
+ emits *both* rules, leaving the winner to Tailwind's output order rather
111
+ than to you. So an override worked on one prop and silently did nothing on
112
+ the next, with no way to tell which you were getting. All 27 sites now merge
113
+ through `tailwind-merge`, which *removes* the conflicting default:
114
+ `gap-8` beats `gap-4`, and `p-8` beats `px-3 py-2`.
115
+
116
+ ### Added
117
+
118
+ - **`cn` is exported from `/ui`** - the same merge the components use, for
119
+ composing your own on top of them.
120
+
121
+ - **`className` on `<ConnectPageShell>`, `<ConnectCursorPagination>` and
122
+ `<ConnectFilterTabs>`**, which had no way to be styled at all. Components
123
+ that render only `children` (`<ConnectPlatformGate>`,
124
+ `<ConnectWorkspaceTypeGate>`, `<MotionProvider>`) still take none, because
125
+ they have no element to put it on.
126
+
127
+ ## 0.25.1
128
+
129
+ ### Fixed
130
+
131
+ - **`<ConnectFilterTabs>` chips had almost no padding.** They were drawn at
132
+ `size="sm"`, which in `@heroui/styles` is not a smaller chip but
133
+ `px-1 py-0` - four pixels of horizontal padding and none at all vertically.
134
+ That is reasonable on a label chip and wrong on the primary control of a
135
+ list: the filter strip read as a row of bare words, and the press target
136
+ was barely taller than its own text on a control people hit constantly,
137
+ often with a thumb. Now explicit `px-3.5 py-1.5` with a `min-h-8`, so a
138
+ chip carrying a count and one without still share a shape.
139
+
140
+ - **A hand-rolled `<button>` showed an arrow cursor, not a pointer.**
141
+ Tailwind v4's Preflight sets `cursor: default` on `button`, reversing v3,
142
+ and HeroUI restores the pointer only on its own classes via
143
+ `--cursor-interactive`. So every plain `<button>` an app writes - a menu
144
+ row, a filter chip, a calendar day, a card that is really a press target -
145
+ rendered as though it were text. `theme.css` now corrects it once, at zero
146
+ specificity via `:where()`, so component rules including HeroUI's own
147
+ disabled handling still win. Every application platform importing the theme
148
+ picks it up.
149
+
6
150
  ## 0.25.0
7
151
 
8
152
  ### Changed
package/README.md CHANGED
@@ -345,7 +345,7 @@ try {
345
345
  | `contacts` | `lookupByPhone` · `get` |
346
346
  | `channels` | `list` · `get` · `claim` · `setRouting` · `clearRouting` |
347
347
  | `calls` | `list` · `get` |
348
- | `scheduling` | `listEventTypes` · `findSlots` · `listBookings` · `createBooking` · `rescheduleBooking` · `cancelBooking` |
348
+ | `scheduling` | `listEventTypes` · `findSlots` · `listBookings` · `getBooking` · `createBooking` · `rescheduleBooking` · `cancelBooking` |
349
349
  | `ai` | `chat` · `extract` |
350
350
  | `otp` | `send` · `verify` |
351
351
  | `logs` | `write` |
@@ -404,6 +404,26 @@ try {
404
404
  `timezone` is the **business's** zone, not your user's - render the wall time in
405
405
  it, or you will offer an hour they are not open.
406
406
 
407
+ Keeping the booking id on a record of your own - a meeting on a deal, an
408
+ appointment on a case - is how you get back to it later:
409
+
410
+ ```ts
411
+ const booking = await client.scheduling.getBooking({ workspaceId, bookingId });
412
+
413
+ // Someone moved it on the workspace's own calendar. Follow the pointer.
414
+ if (booking.status === "rescheduled") {
415
+ const current = await client.scheduling.getBooking({
416
+ workspaceId,
417
+ bookingId: booking.rescheduledToId!,
418
+ });
419
+ }
420
+ ```
421
+
422
+ Use `getBooking`, not `listBookings`, for that. The list is a window over
423
+ `startAt`, and the `startAt` you cached is exactly what is wrong once the
424
+ appointment has moved - so the window you build from it comes back without the
425
+ row, in the one case worth re-reading for.
426
+
407
427
  ### Attributing a log to a user
408
428
 
409
429
  ```ts
@@ -11,6 +11,7 @@ const helpers_js_1 = require("../../helpers.js");
11
11
  const provider_js_1 = require("../provider.js");
12
12
  const motion_js_1 = require("./motion.js");
13
13
  const readiness_js_1 = require("./readiness.js");
14
+ const cn_js_1 = require("../../cn.js");
14
15
  function formatDate(iso) {
15
16
  return new Date(iso).toLocaleDateString(undefined, { day: "numeric", month: "short", year: "numeric" });
16
17
  }
@@ -121,15 +122,40 @@ function useConnectAccessGate() {
121
122
  * entire app must not hide a public landing page behind an empty gate. Your
122
123
  * own sign-in flow decides what a signed-out visitor sees.
123
124
  *
125
+ * **While the verdict is unknown, `children` render.** The two reads behind
126
+ * this gate are client-side round trips that happen on *every* page load, and
127
+ * this component is mounted around an entire application - so blocking on
128
+ * them replaced every partner's whole page with one placeholder until both
129
+ * came back. Anything the host had streamed underneath was discarded: per-
130
+ * section skeletons never painted, and a page built as a static shell with
131
+ * suspended regions arrived all at once anyway. That is the common path, paid
132
+ * by every workspace on every navigation.
133
+ *
134
+ * Rendering `children` instead makes the loading case agree with the two
135
+ * cases either side of it. A *failed* read already falls through to `clear`
136
+ * ("fail open rather than hard-block the whole app on a network hiccup", in
137
+ * `useConnectAccessGate`), and a signed-out visitor already renders
138
+ * `children` too - "we do not know yet" was the one unknown treated as a
139
+ * refusal.
140
+ *
141
+ * What it costs is honest and small: a genuinely blocked workspace sees its
142
+ * app for the length of one round trip before the block card replaces it.
143
+ * That is the rare path, it is self-correcting, and it is the same exposure
144
+ * the failed-read branch has always accepted. Pass `loading` to opt back into
145
+ * a placeholder for a gate around one small region.
146
+ *
124
147
  * **This is a UX gate, not a security boundary** - same caveat as every
125
- * other gate in this package. The authoritative check is
126
- * `client.entitlements.getReadiness` on your server, before you do the
127
- * thing.
148
+ * other gate in this package, and the reason the trade above is available at
149
+ * all. The authoritative check is `client.entitlements.getReadiness` on your
150
+ * server, before you do the thing.
128
151
  */
129
152
  function ConnectAccessGate({ children, fallback, loading, className }) {
130
153
  const gate = useConnectAccessGate();
154
+ // `children`, unless the host explicitly asked for a placeholder. See the
155
+ // doc comment - this is the branch that decides whether a partner's page
156
+ // can stream.
131
157
  if (gate.state === "loading") {
132
- return (0, jsx_runtime_1.jsx)(jsx_runtime_1.Fragment, { children: loading ?? (0, jsx_runtime_1.jsx)(react_2.Skeleton, { className: `h-32 rounded-xl ${className ?? ""}` }) });
158
+ return (0, jsx_runtime_1.jsx)(jsx_runtime_1.Fragment, { children: loading ?? children });
133
159
  }
134
160
  if (gate.state === "signed_out" || gate.state === "clear") {
135
161
  return (0, jsx_runtime_1.jsx)(jsx_runtime_1.Fragment, { children: children });
@@ -208,7 +234,7 @@ function PaymentRetryBanner({ blocker, className, }) {
208
234
  const [dismissed, setDismissed] = (0, react_1.useState)(() => dismissKey !== null && typeof window !== "undefined" && window.localStorage.getItem(dismissKey) === "1");
209
235
  if (dismissed)
210
236
  return null;
211
- return ((0, jsx_runtime_1.jsx)(motion_js_1.MotionProvider, { children: (0, jsx_runtime_1.jsxs)(motion_js_1.m.div, { className: `flex flex-wrap items-center justify-between gap-3 rounded-lg bg-accent-soft px-3 py-2 text-sm text-accent ${className ?? ""}`, role: "status", initial: { opacity: 0, y: -6 }, animate: { opacity: 1, y: 0 }, transition: motion_js_1.ENTER, children: [(0, jsx_runtime_1.jsxs)("span", { className: "flex items-center gap-2", children: [(0, jsx_runtime_1.jsx)(lucide_react_1.AlertTriangle, { className: "size-4 shrink-0", "aria-hidden": "true" }), blocker.message, blocker.graceEndsAt && ` Resolve by ${formatDate(blocker.graceEndsAt)} to avoid losing access.`] }), (0, jsx_runtime_1.jsxs)("div", { className: "flex items-center gap-2", children: [blocker.resolveUrl && ((0, jsx_runtime_1.jsxs)(react_2.Button, { variant: "secondary", onPress: () => window.open(blocker.resolveUrl, "_blank", "noopener,noreferrer"), children: ["Resolve payment", (0, jsx_runtime_1.jsx)(lucide_react_1.ExternalLink, { className: "size-3.5" })] })), (0, jsx_runtime_1.jsx)(react_2.Button, { variant: "tertiary", isIconOnly: true, "aria-label": "Dismiss", onPress: () => {
237
+ return ((0, jsx_runtime_1.jsx)(motion_js_1.MotionProvider, { children: (0, jsx_runtime_1.jsxs)(motion_js_1.m.div, { className: (0, cn_js_1.cn)("flex flex-wrap items-center justify-between gap-3 rounded-lg bg-accent-soft px-3 py-2 text-sm text-accent", className), role: "status", initial: { opacity: 0, y: -6 }, animate: { opacity: 1, y: 0 }, transition: motion_js_1.ENTER, children: [(0, jsx_runtime_1.jsxs)("span", { className: "flex items-center gap-2", children: [(0, jsx_runtime_1.jsx)(lucide_react_1.AlertTriangle, { className: "size-4 shrink-0", "aria-hidden": "true" }), blocker.message, blocker.graceEndsAt && ` Resolve by ${formatDate(blocker.graceEndsAt)} to avoid losing access.`] }), (0, jsx_runtime_1.jsxs)("div", { className: "flex items-center gap-2", children: [blocker.resolveUrl && ((0, jsx_runtime_1.jsxs)(react_2.Button, { variant: "secondary", onPress: () => window.open(blocker.resolveUrl, "_blank", "noopener,noreferrer"), children: ["Resolve payment", (0, jsx_runtime_1.jsx)(lucide_react_1.ExternalLink, { className: "size-3.5" })] })), (0, jsx_runtime_1.jsx)(react_2.Button, { variant: "tertiary", isIconOnly: true, "aria-label": "Dismiss", onPress: () => {
212
238
  setDismissed(true);
213
239
  if (dismissKey && typeof window !== "undefined")
214
240
  window.localStorage.setItem(dismissKey, "1");
@@ -10,6 +10,7 @@ const provider_js_1 = require("../provider.js");
10
10
  const motion_js_1 = require("./motion.js");
11
11
  const surfaces_js_1 = require("./surfaces.js");
12
12
  const format_relative_time_js_1 = require("../../format-relative-time.js");
13
+ const cn_js_1 = require("../../cn.js");
13
14
  const DEVICE_ICONS = {
14
15
  desktop: lucide_react_1.Monitor,
15
16
  mobile: lucide_react_1.Smartphone,
@@ -80,13 +81,13 @@ function ConnectAccountSessions({ className, maxVisible = 4 }) {
80
81
  const [isRevokingOthers, setIsRevokingOthers] = (0, react_1.useState)(false);
81
82
  const [showAll, setShowAll] = (0, react_1.useState)(false);
82
83
  if (isLoading) {
83
- return ((0, jsx_runtime_1.jsxs)("section", { className: `flex flex-col gap-3 ${className ?? ""}`, children: [(0, jsx_runtime_1.jsx)(react_2.Skeleton, { className: "h-4 w-28" }), (0, jsx_runtime_1.jsx)("div", { className: `flex flex-col divide-y divide-border overflow-hidden rounded-xl border border-border ${surfaces_js_1.PANEL_FILL}`, children: Array.from({ length: 2 }, (_, index) => ((0, jsx_runtime_1.jsxs)("div", { className: "flex items-center gap-3 px-3 py-3", children: [(0, jsx_runtime_1.jsx)(react_2.Skeleton, { className: "size-9 shrink-0 rounded-xl" }), (0, jsx_runtime_1.jsxs)("div", { className: "flex min-w-0 flex-1 flex-col gap-1.5", children: [(0, jsx_runtime_1.jsx)(react_2.Skeleton, { className: "h-3.5 w-32" }), (0, jsx_runtime_1.jsx)(react_2.Skeleton, { className: "h-3 w-24" })] })] }, index))) })] }));
84
+ return ((0, jsx_runtime_1.jsxs)("section", { className: (0, cn_js_1.cn)("flex flex-col gap-3", className), children: [(0, jsx_runtime_1.jsx)(react_2.Skeleton, { className: "h-4 w-28" }), (0, jsx_runtime_1.jsx)("div", { className: `flex flex-col divide-y divide-border overflow-hidden rounded-xl border border-border ${surfaces_js_1.PANEL_FILL}`, children: Array.from({ length: 2 }, (_, index) => ((0, jsx_runtime_1.jsxs)("div", { className: "flex items-center gap-3 px-3 py-3", children: [(0, jsx_runtime_1.jsx)(react_2.Skeleton, { className: "size-9 shrink-0 rounded-xl" }), (0, jsx_runtime_1.jsxs)("div", { className: "flex min-w-0 flex-1 flex-col gap-1.5", children: [(0, jsx_runtime_1.jsx)(react_2.Skeleton, { className: "h-3.5 w-32" }), (0, jsx_runtime_1.jsx)(react_2.Skeleton, { className: "h-3 w-24" })] })] }, index))) })] }));
84
85
  }
85
86
  // A failed read used to render as *nothing*, which is indistinguishable
86
87
  // from "you have no other devices" - the one reading of this list that
87
88
  // should never be guessed at.
88
89
  if (error) {
89
- return ((0, jsx_runtime_1.jsxs)("section", { className: `flex flex-col gap-2 ${className ?? ""}`, children: [(0, jsx_runtime_1.jsx)("p", { className: "text-sm font-medium text-foreground", children: "Active sessions" }), (0, jsx_runtime_1.jsx)("p", { className: `rounded-xl border border-border ${surfaces_js_1.PANEL_FILL} px-3 py-4 text-xs text-muted`, children: "Could not load your sessions. Try again in a moment." })] }));
90
+ return ((0, jsx_runtime_1.jsxs)("section", { className: (0, cn_js_1.cn)("flex flex-col gap-2", className), children: [(0, jsx_runtime_1.jsx)("p", { className: "text-sm font-medium text-foreground", children: "Active sessions" }), (0, jsx_runtime_1.jsx)("p", { className: `rounded-xl border border-border ${surfaces_js_1.PANEL_FILL} px-3 py-4 text-xs text-muted`, children: "Could not load your sessions. Try again in a moment." })] }));
90
91
  }
91
92
  const sessions = ordered(data?.items ?? []);
92
93
  if (sessions.length === 0)
@@ -94,7 +95,7 @@ function ConnectAccountSessions({ className, maxVisible = 4 }) {
94
95
  const hasOtherSessions = sessions.some((session) => !session.isCurrent);
95
96
  const isCollapsible = maxVisible > 0 && sessions.length > maxVisible;
96
97
  const visible = isCollapsible && !showAll ? sessions.slice(0, maxVisible) : sessions;
97
- return ((0, jsx_runtime_1.jsx)(motion_js_1.MotionProvider, { children: (0, jsx_runtime_1.jsxs)("section", { className: `flex flex-col gap-3 ${className ?? ""}`, children: [(0, jsx_runtime_1.jsxs)("div", { className: "flex items-start justify-between gap-3", children: [(0, jsx_runtime_1.jsxs)("div", { className: "min-w-0", children: [(0, jsx_runtime_1.jsxs)("p", { className: "flex items-center gap-2 text-sm font-medium text-foreground", children: ["Active sessions", (0, jsx_runtime_1.jsx)(react_2.Chip, { size: "sm", variant: "soft", children: sessions.length })] }), (0, jsx_runtime_1.jsx)("p", { className: "text-xs text-muted", children: "Devices signed in to your Connect account." })] }), hasOtherSessions && ((0, jsx_runtime_1.jsx)(react_2.Button, { size: "sm", variant: "tertiary", className: "shrink-0 text-xs text-danger", isPending: isRevokingOthers, isDisabled: isRevokingOthers, onPress: () => {
98
+ return ((0, jsx_runtime_1.jsx)(motion_js_1.MotionProvider, { children: (0, jsx_runtime_1.jsxs)("section", { className: (0, cn_js_1.cn)("flex flex-col gap-3", className), children: [(0, jsx_runtime_1.jsxs)("div", { className: "flex items-start justify-between gap-3", children: [(0, jsx_runtime_1.jsxs)("div", { className: "min-w-0", children: [(0, jsx_runtime_1.jsxs)("p", { className: "flex items-center gap-2 text-sm font-medium text-foreground", children: ["Active sessions", (0, jsx_runtime_1.jsx)(react_2.Chip, { size: "sm", variant: "soft", children: sessions.length })] }), (0, jsx_runtime_1.jsx)("p", { className: "text-xs text-muted", children: "Devices signed in to your Connect account." })] }), hasOtherSessions && ((0, jsx_runtime_1.jsx)(react_2.Button, { size: "sm", variant: "tertiary", className: "shrink-0 text-xs text-danger", isPending: isRevokingOthers, isDisabled: isRevokingOthers, onPress: () => {
98
99
  setIsRevokingOthers(true);
99
100
  void mutateResource("/sessions/revoke-others")
100
101
  .then(() => {
@@ -20,8 +20,18 @@ const workspace_switcher_js_1 = require("./workspace-switcher.js");
20
20
  const user_menu_js_1 = require("./user-menu.js");
21
21
  const nav_js_1 = require("./nav.js");
22
22
  const use_active_path_js_1 = require("../use-active-path.js");
23
+ const cn_js_1 = require("../../cn.js");
23
24
  const COLLAPSED_KEY = "connect_sidenav_collapsed";
24
25
  const COLLAPSED_EVENT = "connect:sidenav-collapsed";
26
+ /**
27
+ * The `<main>` element's id, and the skip link's target.
28
+ *
29
+ * Module-local rather than a prop: both ends of the pair live in this file, so
30
+ * there is nothing for a caller to keep in step - and a partner supplying
31
+ * their own id could only get it wrong. Namespaced because it lands in a
32
+ * document the partner also writes.
33
+ */
34
+ const CONTENT_ID = "connect-main-content";
25
35
  /**
26
36
  * The stored rail preference, or `null` when there isn't one (or when reading
27
37
  * it is not allowed).
@@ -188,5 +198,5 @@ function AppShell({ name, icon, homeHref = "/", nav, secondaryNav, activePath, r
188
198
  }), [renderLink, activePath, closeMobile]);
189
199
  const accountRow = signedOut ? (showAuthButtons && ((0, jsx_runtime_1.jsxs)("div", { className: "flex flex-col gap-2", children: [(0, jsx_runtime_1.jsx)(auth_buttons_js_1.ConnectLoginButton, { size: "sm", fullWidth: true }), (0, jsx_runtime_1.jsx)(auth_buttons_js_1.ConnectRegisterButton, { size: "sm", fullWidth: true })] }))) : ((0, jsx_runtime_1.jsx)(user_menu_js_1.ConnectUserButton, { variant: "row", theme: theme, onThemeChange: onThemeChange }));
190
200
  const railContent = ((0, jsx_runtime_1.jsxs)(jsx_runtime_1.Fragment, { children: [(0, jsx_runtime_1.jsx)(sidebar_js_1.ConnectSidebarHeader, { name: name, icon: icon, homeHref: homeHref }), workspaceSwitcher !== false && ((0, jsx_runtime_1.jsx)("div", { className: "shrink-0 px-3 pb-1", children: (0, jsx_runtime_1.jsx)(workspace_switcher_js_1.ConnectWorkspaceSwitcher, {}) })), (0, jsx_runtime_1.jsx)(sidebar_js_1.ConnectSidebarBody, { children: (0, jsx_runtime_1.jsx)(side_nav_js_1.ConnectSidebarNav, { nav: visibleNav }) }), (visibleSecondaryNav || sidebarFooter || showAppsGrid) && ((0, jsx_runtime_1.jsxs)(sidebar_js_1.ConnectSidebarFooter, { separated: false, children: [visibleSecondaryNav && (0, jsx_runtime_1.jsx)(side_nav_js_1.ConnectSidebarNav, { nav: visibleSecondaryNav }), sidebarFooter, showAppsGrid && (0, jsx_runtime_1.jsx)(apps_grid_js_1.ConnectAppsGrid, { variant: "row" })] })), accountRow && (0, jsx_runtime_1.jsx)(sidebar_js_1.ConnectSidebarFooter, { children: accountRow })] }));
191
- return ((0, jsx_runtime_1.jsx)(motion_js_1.MotionProvider, { children: (0, jsx_runtime_1.jsxs)("div", { className: `flex h-screen bg-background ${className ?? ""}`, children: [(0, jsx_runtime_1.jsx)(shell_context_js_1.ConnectShellProvider, { value: railShell, children: (0, jsx_runtime_1.jsx)(sidebar_js_1.ConnectSidebar, { children: railContent }) }), (0, jsx_runtime_1.jsx)(react_2.Drawer.Backdrop, { isOpen: mobileOpen, onOpenChange: setMobileOpen, children: (0, jsx_runtime_1.jsx)(react_2.Drawer.Content, { placement: "left", className: "w-[min(20rem,85vw)]", children: (0, jsx_runtime_1.jsx)(react_2.Drawer.Dialog, { "aria-label": "Navigation menu", className: "p-0", children: (0, jsx_runtime_1.jsx)(shell_context_js_1.ConnectShellProvider, { value: drawerShell, children: (0, jsx_runtime_1.jsx)("div", { className: "flex h-full flex-col", children: railContent }) }) }) }) }), (0, jsx_runtime_1.jsxs)("div", { className: "flex min-w-0 flex-1 flex-col", children: [(0, jsx_runtime_1.jsxs)("header", { className: "flex h-14 shrink-0 items-center gap-2 border-b border-border bg-surface px-3 md:hidden", children: [(0, jsx_runtime_1.jsx)(react_2.Button, { isIconOnly: true, variant: "tertiary", size: "sm", "aria-label": "Open navigation menu", onPress: () => setMobileOpen(true), children: (0, jsx_runtime_1.jsx)(lucide_react_1.Menu, { className: "size-5" }) }), (0, jsx_runtime_1.jsx)(logo_js_1.ConnectLogo, { name: name, icon: icon })] }), (0, jsx_runtime_1.jsx)("main", { className: "flex-1 overflow-y-auto", children: (0, jsx_runtime_1.jsxs)("div", { className: contentClassName ?? "mx-auto w-full max-w-6xl px-4 py-6 md:px-8 md:py-8", children: [gated ? (0, jsx_runtime_1.jsx)(access_gate_js_1.ConnectAccessGate, { children: children }) : children, footer] }) })] }), askAi && (0, jsx_runtime_1.jsx)(ask_ai_js_1.ConnectAskAi, { ...(askAi === true ? {} : askAi) })] }) }));
201
+ return ((0, jsx_runtime_1.jsx)(motion_js_1.MotionProvider, { children: (0, jsx_runtime_1.jsxs)("div", { className: (0, cn_js_1.cn)("flex h-dvh bg-background", className), children: [(0, jsx_runtime_1.jsx)("a", { href: `#${CONTENT_ID}`, className: "sr-only focus:not-sr-only focus:absolute focus:start-3 focus:top-3 focus:z-50 focus:rounded-lg focus:bg-surface focus:px-3 focus:py-2 focus:text-sm focus:font-medium focus:text-foreground focus:shadow-lg focus:outline-2 focus:outline-offset-2 focus:outline-focus", children: "Skip to content" }), (0, jsx_runtime_1.jsx)(shell_context_js_1.ConnectShellProvider, { value: railShell, children: (0, jsx_runtime_1.jsx)(sidebar_js_1.ConnectSidebar, { children: railContent }) }), (0, jsx_runtime_1.jsx)(react_2.Drawer.Backdrop, { isOpen: mobileOpen, onOpenChange: setMobileOpen, children: (0, jsx_runtime_1.jsx)(react_2.Drawer.Content, { placement: "left", className: "w-[min(20rem,85vw)]", children: (0, jsx_runtime_1.jsxs)(react_2.Drawer.Dialog, { "aria-label": "Navigation menu", className: "p-0 pt-[env(safe-area-inset-top)] pb-[env(safe-area-inset-bottom)]", children: [(0, jsx_runtime_1.jsx)(react_2.Drawer.CloseTrigger, { className: "end-3 top-[calc(--spacing(3)+env(safe-area-inset-top))]" }), (0, jsx_runtime_1.jsx)(shell_context_js_1.ConnectShellProvider, { value: drawerShell, children: (0, jsx_runtime_1.jsx)("div", { className: "flex h-full flex-col [&>:first-child]:pe-10", children: railContent }) })] }) }) }), (0, jsx_runtime_1.jsxs)("div", { className: "flex min-w-0 flex-1 flex-col", children: [(0, jsx_runtime_1.jsxs)("header", { className: "flex h-14 shrink-0 items-center gap-2 border-b border-border bg-surface px-3 md:hidden", children: [(0, jsx_runtime_1.jsx)(react_2.Button, { isIconOnly: true, variant: "tertiary", size: "sm", "aria-label": "Open navigation menu", onPress: () => setMobileOpen(true), children: (0, jsx_runtime_1.jsx)(lucide_react_1.Menu, { className: "size-5" }) }), (0, jsx_runtime_1.jsx)(logo_js_1.ConnectLogo, { name: name, icon: icon })] }), (0, jsx_runtime_1.jsx)("main", { id: CONTENT_ID, tabIndex: -1, className: "flex-1 overflow-y-auto outline-none", children: (0, jsx_runtime_1.jsxs)("div", { className: contentClassName ?? "w-full px-4 py-5 md:px-6 md:py-6", children: [gated ? (0, jsx_runtime_1.jsx)(access_gate_js_1.ConnectAccessGate, { children: children }) : children, footer] }) })] }), askAi && (0, jsx_runtime_1.jsx)(ask_ai_js_1.ConnectAskAi, { ...(askAi === true ? {} : askAi) })] }) }));
192
202
  }
@@ -11,6 +11,7 @@ const empty_state_js_1 = require("../../ui/empty-state.js");
11
11
  const use_scheduling_js_1 = require("../use-scheduling.js");
12
12
  const booking_grid_js_1 = require("./booking-grid.js");
13
13
  const booking_dialogs_js_1 = require("./booking-dialogs.js");
14
+ const cn_js_1 = require("../../cn.js");
14
15
  /**
15
16
  * The visible band of the day. Outside it a booking is drawn at the edge and
16
17
  * flagged rather than hidden - see `layoutBookings`.
@@ -68,7 +69,7 @@ function ConnectBookingCalendar({ timeZone, hostMemberId, window: visibleWindow
68
69
  const marks = (0, react_2.useMemo)(() => (0, booking_grid_js_1.hourMarks)(visibleWindow), [visibleWindow]);
69
70
  const shiftWeek = (weeks) => setWeekAnchor((anchor) => new Date(anchor.getTime() + weeks * 7 * 24 * 60 * 60_000));
70
71
  const eventTypeById = (0, react_2.useMemo)(() => new Map(eventTypes.map((eventType) => [eventType.id, eventType])), [eventTypes]);
71
- return ((0, jsx_runtime_1.jsxs)("div", { className: `flex flex-col gap-4 ${className ?? ""}`, children: [(0, jsx_runtime_1.jsxs)("header", { className: "flex flex-wrap items-center justify-between gap-3", children: [(0, jsx_runtime_1.jsxs)("div", { className: "flex items-center gap-1", children: [(0, jsx_runtime_1.jsx)(react_1.Button, { isIconOnly: true, size: "sm", variant: "tertiary", "aria-label": "Previous week", onPress: () => shiftWeek(-1), children: (0, jsx_runtime_1.jsx)(lucide_react_1.ChevronLeft, { className: "size-4", "aria-hidden": true }) }), (0, jsx_runtime_1.jsx)(react_1.Button, { size: "sm", variant: "secondary", onPress: () => setWeekAnchor(startOfWeek(new Date(), zone)), children: "Today" }), (0, jsx_runtime_1.jsx)(react_1.Button, { isIconOnly: true, size: "sm", variant: "tertiary", "aria-label": "Next week", onPress: () => shiftWeek(1), children: (0, jsx_runtime_1.jsx)(lucide_react_1.ChevronRight, { className: "size-4", "aria-hidden": true }) }), (0, jsx_runtime_1.jsx)("p", { className: "ml-2 text-sm font-medium", children: weekLabel(columns, zone) })] }), canBook && eventTypes.length > 0 && ((0, jsx_runtime_1.jsx)(react_1.Button, { size: "sm", variant: "primary", onPress: () => setBooking({ startAt: nextHour(now) }), children: "New appointment" }))] }), error && (0, jsx_runtime_1.jsx)("p", { className: "text-sm text-danger", children: error }), isLoading ? ((0, jsx_runtime_1.jsx)(react_1.Skeleton, { className: "h-96 w-full rounded-lg" })) : ((0, jsx_runtime_1.jsxs)(jsx_runtime_1.Fragment, { children: [(0, jsx_runtime_1.jsx)(react_1.Card, { className: "hidden overflow-hidden sm:block", children: (0, jsx_runtime_1.jsx)(WeekGrid, { columns: columns, marks: marks, window: visibleWindow, positioned: positioned, eventTypeById: eventTypeById, zone: zone, onSelect: setSelected, onPickEmpty: canBook ? (startAt) => setBooking({ startAt }) : undefined }) }), (0, jsx_runtime_1.jsx)("div", { className: "sm:hidden", children: (0, jsx_runtime_1.jsx)(AgendaView, { columns: columns, bookings: bookings, eventTypeById: eventTypeById, zone: zone, onSelect: setSelected }) })] })), booking && ((0, jsx_runtime_1.jsx)(booking_dialogs_js_1.ConnectBookingDialog, { isOpen: true, onOpenChange: (open) => !open && setBooking(null), eventTypes: eventTypes, initialStartAt: booking.startAt, timeZone: zone, onBooked: () => {
72
+ return ((0, jsx_runtime_1.jsxs)("div", { className: (0, cn_js_1.cn)("flex flex-col gap-4", className), children: [(0, jsx_runtime_1.jsxs)("header", { className: "flex flex-wrap items-center justify-between gap-3", children: [(0, jsx_runtime_1.jsxs)("div", { className: "flex items-center gap-1", children: [(0, jsx_runtime_1.jsx)(react_1.Button, { isIconOnly: true, size: "sm", variant: "tertiary", "aria-label": "Previous week", onPress: () => shiftWeek(-1), children: (0, jsx_runtime_1.jsx)(lucide_react_1.ChevronLeft, { className: "size-4", "aria-hidden": true }) }), (0, jsx_runtime_1.jsx)(react_1.Button, { size: "sm", variant: "secondary", onPress: () => setWeekAnchor(startOfWeek(new Date(), zone)), children: "Today" }), (0, jsx_runtime_1.jsx)(react_1.Button, { isIconOnly: true, size: "sm", variant: "tertiary", "aria-label": "Next week", onPress: () => shiftWeek(1), children: (0, jsx_runtime_1.jsx)(lucide_react_1.ChevronRight, { className: "size-4", "aria-hidden": true }) }), (0, jsx_runtime_1.jsx)("p", { className: "ml-2 text-sm font-medium", children: weekLabel(columns, zone) })] }), canBook && eventTypes.length > 0 && ((0, jsx_runtime_1.jsx)(react_1.Button, { size: "sm", variant: "primary", onPress: () => setBooking({ startAt: nextHour(now) }), children: "New appointment" }))] }), error && (0, jsx_runtime_1.jsx)("p", { className: "text-sm text-danger", children: error }), isLoading ? ((0, jsx_runtime_1.jsx)(react_1.Skeleton, { className: "h-96 w-full rounded-lg" })) : ((0, jsx_runtime_1.jsxs)(jsx_runtime_1.Fragment, { children: [(0, jsx_runtime_1.jsx)(react_1.Card, { className: "hidden overflow-hidden sm:block", children: (0, jsx_runtime_1.jsx)(WeekGrid, { columns: columns, marks: marks, window: visibleWindow, positioned: positioned, eventTypeById: eventTypeById, zone: zone, onSelect: setSelected, onPickEmpty: canBook ? (startAt) => setBooking({ startAt }) : undefined }) }), (0, jsx_runtime_1.jsx)("div", { className: "sm:hidden", children: (0, jsx_runtime_1.jsx)(AgendaView, { columns: columns, bookings: bookings, eventTypeById: eventTypeById, zone: zone, onSelect: setSelected }) })] })), booking && ((0, jsx_runtime_1.jsx)(booking_dialogs_js_1.ConnectBookingDialog, { isOpen: true, onOpenChange: (open) => !open && setBooking(null), eventTypes: eventTypes, initialStartAt: booking.startAt, timeZone: zone, onBooked: () => {
72
73
  setBooking(null);
73
74
  refetch();
74
75
  } })), selected && ((0, jsx_runtime_1.jsx)(booking_dialogs_js_1.ConnectBookingDetail, { booking: selected, eventType: eventTypeById.get(selected.eventTypeId) ?? null, timeZone: zone, onOpenChange: (open) => !open && setSelected(null), onChanged: () => {
@@ -4,6 +4,7 @@ Object.defineProperty(exports, "__esModule", { value: true });
4
4
  exports.ConnectCursorPagination = ConnectCursorPagination;
5
5
  const jsx_runtime_1 = require("react/jsx-runtime");
6
6
  const react_1 = require("@heroui/react");
7
+ const cn_js_1 = require("../../cn.js");
7
8
  /**
8
9
  * Next / previous for a cursor-paginated list.
9
10
  *
@@ -17,6 +18,6 @@ const react_1 = require("@heroui/react");
17
18
  * filtering or paging changed the result set. Without it the table silently
18
19
  * swaps its contents.
19
20
  */
20
- function ConnectCursorPagination({ shown, nextCursor, hasPrevious, onNext, onPrevious, isPending = false, noun = ["row", "rows"], }) {
21
- return ((0, jsx_runtime_1.jsxs)("div", { className: "flex items-center justify-between gap-3 pt-3", children: [(0, jsx_runtime_1.jsxs)("p", { "aria-live": "polite", className: "text-xs text-muted", children: [shown, " ", shown === 1 ? noun[0] : noun[1]] }), (0, jsx_runtime_1.jsxs)("div", { className: "flex gap-2", children: [(0, jsx_runtime_1.jsx)(react_1.Button, { size: "sm", variant: "secondary", isDisabled: !hasPrevious || isPending, onPress: onPrevious, children: "Previous" }), (0, jsx_runtime_1.jsx)(react_1.Button, { size: "sm", variant: "secondary", isDisabled: nextCursor === null || isPending, onPress: () => nextCursor && onNext(nextCursor), children: "Next" })] })] }));
21
+ function ConnectCursorPagination({ shown, nextCursor, hasPrevious, onNext, onPrevious, isPending = false, noun = ["row", "rows"], className, }) {
22
+ return ((0, jsx_runtime_1.jsxs)("div", { className: (0, cn_js_1.cn)("flex items-center justify-between gap-3 pt-3", className), children: [(0, jsx_runtime_1.jsxs)("p", { "aria-live": "polite", className: "text-xs text-muted", children: [shown, " ", shown === 1 ? noun[0] : noun[1]] }), (0, jsx_runtime_1.jsxs)("div", { className: "flex gap-2", children: [(0, jsx_runtime_1.jsx)(react_1.Button, { size: "sm", variant: "secondary", isDisabled: !hasPrevious || isPending, onPress: onPrevious, children: "Previous" }), (0, jsx_runtime_1.jsx)(react_1.Button, { size: "sm", variant: "secondary", isDisabled: nextCursor === null || isPending, onPress: () => nextCursor && onNext(nextCursor), children: "Next" })] })] }));
22
23
  }
@@ -4,6 +4,7 @@ Object.defineProperty(exports, "__esModule", { value: true });
4
4
  exports.ConnectFilterTabs = ConnectFilterTabs;
5
5
  const jsx_runtime_1 = require("react/jsx-runtime");
6
6
  const react_1 = require("@heroui/react");
7
+ const cn_js_1 = require("../../cn.js");
7
8
  /**
8
9
  * A single-select filter strip - the "All / Open / Won / Lost" row above a
9
10
  * list.
@@ -13,12 +14,29 @@ const react_1 = require("@heroui/react");
13
14
  * selected one should be announced as selected. A group of `<button>`s gets
14
15
  * neither, and that is the usual hand-rolled shape.
15
16
  *
16
- * Scrolls horizontally rather than wrapping on a narrow viewport - a filter
17
- * strip that wraps to three lines pushes the list itself off the screen.
17
+ * Wraps rather than scrolling on a narrow viewport. See the note on the
18
+ * container below: a horizontal scroller nested in a vertically scrolling
19
+ * page hides its overflow rather than deferring it.
18
20
  */
19
- function ConnectFilterTabs({ options, value, onChange, label, }) {
20
- return ((0, jsx_runtime_1.jsx)("div", { role: "radiogroup", "aria-label": label, className: "-mx-1 flex gap-2 overflow-x-auto px-1 pb-1", children: options.map((option) => {
21
+ function ConnectFilterTabs({ options, value, onChange, label, className, }) {
22
+ return (
23
+ /*
24
+ * Wraps. It does not scroll.
25
+ *
26
+ * It used to scroll horizontally, on the reasoning that a filter wrapping
27
+ * to three lines pushes the list itself off the screen. That trade was
28
+ * the wrong way round for a mobile-first product: a horizontal scroller
29
+ * inside a vertically scrolling page is a gesture people do not think to
30
+ * try, so the options past the fold were not "one swipe away" - they were
31
+ * invisible. The strip also came to rest mid-option, showing "ll" where
32
+ * "All" should be.
33
+ *
34
+ * Wrapping costs one extra line on a phone and nothing at all above it,
35
+ * where the row has always fitted. Every option is now on screen and
36
+ * reachable without a gesture, which is what a filter is for.
37
+ */
38
+ (0, jsx_runtime_1.jsx)("div", { role: "radiogroup", "aria-label": label, className: (0, cn_js_1.cn)("-mx-1 flex flex-wrap gap-2 px-1", className), children: options.map((option) => {
21
39
  const isSelected = option.value === value;
22
- return ((0, jsx_runtime_1.jsx)("button", { type: "button", role: "radio", "aria-checked": isSelected, onClick: () => onChange(option.value), className: "shrink-0 rounded-full focus-visible:outline focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-accent", children: (0, jsx_runtime_1.jsxs)(react_1.Chip, { size: "sm", variant: isSelected ? "primary" : "soft", color: isSelected ? "accent" : "default", children: [option.label, option.count !== undefined && (0, jsx_runtime_1.jsx)("span", { className: "ml-1 opacity-70", children: option.count })] }) }, option.value ?? "__all"));
40
+ return ((0, jsx_runtime_1.jsx)("button", { type: "button", role: "radio", "aria-checked": isSelected, onClick: () => onChange(option.value), className: "shrink-0 rounded-full focus-visible:outline focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-accent", children: (0, jsx_runtime_1.jsxs)(react_1.Chip, { variant: isSelected ? "primary" : "soft", color: isSelected ? "accent" : "default", className: "min-h-8 px-3.5 py-1.5 text-sm", children: [option.label, option.count !== undefined && (0, jsx_runtime_1.jsx)("span", { className: "ml-1 opacity-70", children: option.count })] }) }, option.value ?? "__all"));
23
41
  }) }));
24
42
  }
@@ -4,6 +4,7 @@ Object.defineProperty(exports, "__esModule", { value: true });
4
4
  exports.ConnectGrievanceNotice = ConnectGrievanceNotice;
5
5
  const jsx_runtime_1 = require("react/jsx-runtime");
6
6
  const provider_js_1 = require("../provider.js");
7
+ const cn_js_1 = require("../../cn.js");
7
8
  /**
8
9
  * The DPDP s.13 grievance channel, rendered.
9
10
  *
@@ -26,5 +27,5 @@ function ConnectGrievanceNotice({ title = "Grievance redressal", applicationName
26
27
  if (!profile)
27
28
  return null;
28
29
  const { grievance } = profile;
29
- return ((0, jsx_runtime_1.jsxs)("section", { className: className ?? "space-y-3", children: [title !== null && (0, jsx_runtime_1.jsx)("h3", { className: "text-base font-semibold", children: title }), (0, jsx_runtime_1.jsxs)("p", { className: "text-sm text-muted", children: [applicationName ? `${applicationName} is operated on ` : "This service runs on ", "the CosmoOps Connect platform. ", (0, jsx_runtime_1.jsx)("strong", { className: "font-medium text-foreground", children: profile.legalName }), " ", "is the Data Fiduciary for personal data processed here, under the Digital Personal Data Protection Act, 2023."] }), (0, jsx_runtime_1.jsxs)("dl", { className: "grid grid-cols-1 gap-x-6 gap-y-3 text-sm sm:grid-cols-[auto_1fr]", children: [(0, jsx_runtime_1.jsx)("dt", { className: "text-muted", children: "Grievance Officer" }), (0, jsx_runtime_1.jsx)("dd", { className: "text-foreground", children: grievance.officerName }), (0, jsx_runtime_1.jsx)("dt", { className: "text-muted", children: "Email" }), (0, jsx_runtime_1.jsx)("dd", { children: (0, jsx_runtime_1.jsx)("a", { className: "text-accent underline underline-offset-2", href: `mailto:${grievance.officerEmail}`, children: grievance.officerEmail }) }), (0, jsx_runtime_1.jsx)("dt", { className: "text-muted", children: "Contactable" }), (0, jsx_runtime_1.jsx)("dd", { className: "text-foreground", children: grievance.hours }), (0, jsx_runtime_1.jsx)("dt", { className: "text-muted", children: "Registered address" }), (0, jsx_runtime_1.jsx)("dd", { className: "text-foreground", children: profile.registeredAddress })] }), (0, jsx_runtime_1.jsxs)("p", { className: "text-sm text-muted", children: ["Complaints are acknowledged and resolved within ", grievance.responseDays, " days of receipt."] })] }));
30
+ return ((0, jsx_runtime_1.jsxs)("section", { className: (0, cn_js_1.cn)("space-y-3", className), children: [title !== null && (0, jsx_runtime_1.jsx)("h3", { className: "text-base font-semibold", children: title }), (0, jsx_runtime_1.jsxs)("p", { className: "text-sm text-muted", children: [applicationName ? `${applicationName} is operated on ` : "This service runs on ", "the CosmoOps Connect platform. ", (0, jsx_runtime_1.jsx)("strong", { className: "font-medium text-foreground", children: profile.legalName }), " ", "is the Data Fiduciary for personal data processed here, under the Digital Personal Data Protection Act, 2023."] }), (0, jsx_runtime_1.jsxs)("dl", { className: "grid grid-cols-1 gap-x-6 gap-y-3 text-sm sm:grid-cols-[auto_1fr]", children: [(0, jsx_runtime_1.jsx)("dt", { className: "text-muted", children: "Grievance Officer" }), (0, jsx_runtime_1.jsx)("dd", { className: "text-foreground", children: grievance.officerName }), (0, jsx_runtime_1.jsx)("dt", { className: "text-muted", children: "Email" }), (0, jsx_runtime_1.jsx)("dd", { children: (0, jsx_runtime_1.jsx)("a", { className: "text-accent underline underline-offset-2", href: `mailto:${grievance.officerEmail}`, children: grievance.officerEmail }) }), (0, jsx_runtime_1.jsx)("dt", { className: "text-muted", children: "Contactable" }), (0, jsx_runtime_1.jsx)("dd", { className: "text-foreground", children: grievance.hours }), (0, jsx_runtime_1.jsx)("dt", { className: "text-muted", children: "Registered address" }), (0, jsx_runtime_1.jsx)("dd", { className: "text-foreground", children: profile.registeredAddress })] }), (0, jsx_runtime_1.jsxs)("p", { className: "text-sm text-muted", children: ["Complaints are acknowledged and resolved within ", grievance.responseDays, " days of receipt."] })] }));
30
31
  }
@@ -4,6 +4,7 @@ Object.defineProperty(exports, "__esModule", { value: true });
4
4
  exports.ConnectLogo = ConnectLogo;
5
5
  const jsx_runtime_1 = require("react/jsx-runtime");
6
6
  const mark_js_1 = require("../mark.js");
7
+ const cn_js_1 = require("../../cn.js");
7
8
  /**
8
9
  * The shared platform lockup: an application's own glyph on the CosmoOps badge,
9
10
  * `cosmo·ops` beside it, and the application name underneath.
@@ -22,7 +23,7 @@ function ConnectLogo({ name, icon, variant, collapsed = false, className }) {
22
23
  const resolved = variant ?? (collapsed ? "mark" : "full");
23
24
  const badge = ((0, jsx_runtime_1.jsxs)("span", { className: "relative inline-flex shrink-0 items-center justify-center overflow-hidden rounded-[0.28em] bg-accent text-accent-foreground", style: { width: "2em", height: "2em" }, "aria-hidden": "true", children: [icon ? (0, jsx_runtime_1.jsx)("span", { className: "inline-flex items-center justify-center [&>*]:size-[1.25em]", children: icon }) : null, !icon && (0, jsx_runtime_1.jsx)(mark_js_1.ConnectMark, { className: "absolute inset-0 size-full" })] }));
24
25
  if (resolved === "mark") {
25
- return ((0, jsx_runtime_1.jsx)("span", { className: `inline-flex text-sm leading-none ${className ?? ""}`, role: "img", "aria-label": name, children: badge }));
26
+ return ((0, jsx_runtime_1.jsx)("span", { className: (0, cn_js_1.cn)("inline-flex text-sm leading-none", className), role: "img", "aria-label": name, children: badge }));
26
27
  }
27
- return ((0, jsx_runtime_1.jsxs)("span", { className: `inline-flex min-w-0 items-center gap-[0.6em] text-sm leading-none ${className ?? ""}`, role: "img", "aria-label": `cosmoops ${name}`, children: [resolved === "full" && badge, (0, jsx_runtime_1.jsxs)("span", { className: "flex min-w-0 flex-col leading-none", children: [(0, jsx_runtime_1.jsxs)("span", { className: "inline-flex items-center font-extrabold tracking-tight text-foreground", "aria-hidden": "true", children: [(0, jsx_runtime_1.jsx)("span", { children: "cosmo" }), (0, jsx_runtime_1.jsx)("span", { className: "mx-[0.12em] inline-block size-[0.19em] shrink-0 rounded-[0.04em] bg-accent" }), (0, jsx_runtime_1.jsx)("span", { children: "ops" })] }), (0, jsx_runtime_1.jsx)("span", { className: "mt-[0.42em] truncate text-[0.6em] font-semibold uppercase tracking-[0.26em] text-accent", "aria-hidden": "true", children: name })] })] }));
28
+ return ((0, jsx_runtime_1.jsxs)("span", { className: (0, cn_js_1.cn)("inline-flex min-w-0 items-center gap-[0.6em] text-sm leading-none", className), role: "img", "aria-label": `cosmoops ${name}`, children: [resolved === "full" && badge, (0, jsx_runtime_1.jsxs)("span", { className: "flex min-w-0 flex-col leading-none", children: [(0, jsx_runtime_1.jsxs)("span", { className: "inline-flex items-center font-extrabold tracking-tight text-foreground", "aria-hidden": "true", children: [(0, jsx_runtime_1.jsx)("span", { children: "cosmo" }), (0, jsx_runtime_1.jsx)("span", { className: "mx-[0.12em] inline-block size-[0.19em] shrink-0 rounded-[0.04em] bg-accent" }), (0, jsx_runtime_1.jsx)("span", { children: "ops" })] }), (0, jsx_runtime_1.jsx)("span", { className: "mt-[0.42em] truncate text-[0.6em] font-semibold uppercase tracking-[0.26em] text-accent", "aria-hidden": "true", children: name })] })] }));
28
29
  }
@@ -11,6 +11,7 @@ const mark_js_1 = require("../mark.js");
11
11
  const identity_js_1 = require("./identity.js");
12
12
  const motion_js_1 = require("./motion.js");
13
13
  const one_tap_state_js_1 = require("./one-tap-state.js");
14
+ const cn_js_1 = require("../../cn.js");
14
15
  function ConnectOneTap({ onInteractiveSignIn, className = "" }) {
15
16
  const { session, isLoading, signIn, readOneTapHint, oneTapSignIn } = (0, provider_js_1.useConnect)();
16
17
  const [hint, setHint] = (0, react_1.useState)(null);
@@ -95,7 +96,7 @@ function ConnectOneTap({ onInteractiveSignIn, className = "" }) {
95
96
  // Fixed, above ordinary content but below a modal: this is an offer,
96
97
  // and it must never sit on top of something the visitor opened
97
98
  // themselves.
98
- className: `fixed inset-x-3 bottom-3 z-40 sm:inset-x-auto sm:right-4 sm:bottom-4 sm:w-80 ${className}`, onKeyDown: (event) => {
99
+ className: (0, cn_js_1.cn)("fixed inset-x-3 bottom-3 z-40 sm:inset-x-auto sm:right-4 sm:bottom-4 sm:w-80", className), onKeyDown: (event) => {
99
100
  if (event.key === "Escape")
100
101
  dismiss();
101
102
  }, children: (0, jsx_runtime_1.jsxs)(react_2.Card, { className: "p-4 shadow-lg", children: [(0, jsx_runtime_1.jsxs)("div", { className: "flex items-start gap-3", children: [hint.consented ? ((0, jsx_runtime_1.jsxs)(react_2.Avatar, { size: "md", color: "accent", variant: "soft", className: "shrink-0 rounded-full", children: [hint.user?.photoUrl && ((0, jsx_runtime_1.jsx)(react_2.Avatar.Image, { alt: displayName ?? "Your account", src: hint.user.photoUrl })), (0, jsx_runtime_1.jsx)(react_2.Avatar.Fallback, { children: (0, identity_js_1.initials)(hint.user?.displayName ?? null, hint.user?.email ?? null) })] })) : ((0, jsx_runtime_1.jsx)(mark_js_1.ConnectMark, { className: "mt-0.5 size-8 shrink-0" })), (0, jsx_runtime_1.jsxs)("div", { className: "min-w-0 flex-1", children: [(0, jsx_runtime_1.jsx)("p", { className: "truncate text-sm font-medium text-foreground", children: hint.consented ? `Continue as ${displayName ?? "your account"}` : "You're signed in to CosmoOps" }), (0, jsx_runtime_1.jsx)("p", { className: "truncate text-xs text-muted", children: hint.consented
@@ -11,6 +11,7 @@ const use_checkout_js_1 = require("../use-checkout.js");
11
11
  const motion_js_1 = require("./motion.js");
12
12
  const subscribe_dialog_js_1 = require("./subscribe-dialog.js");
13
13
  const plan_pricing_js_1 = require("./plan-pricing.js");
14
+ const cn_js_1 = require("../../cn.js");
14
15
  /**
15
16
  * The plan table: monthly/annual toggle, features, add-ons, the discount
16
17
  * actually in effect, and the subscribe / upgrade / downgrade flows.
@@ -105,7 +106,7 @@ function ConnectPlans({ planIds, hideCoupon = false, className }) {
105
106
  setPending(null);
106
107
  }
107
108
  if (plansLoading || entitlementsLoading) {
108
- return ((0, jsx_runtime_1.jsx)("div", { className: `grid gap-4 sm:grid-cols-2 lg:grid-cols-3 ${className ?? ""}`, children: Array.from({ length: 3 }, (_, index) => ((0, jsx_runtime_1.jsx)(react_1.Skeleton, { className: "h-80 rounded-xl" }, index))) }));
109
+ return ((0, jsx_runtime_1.jsx)("div", { className: (0, cn_js_1.cn)("grid gap-4 sm:grid-cols-2 lg:grid-cols-3", className), children: Array.from({ length: 3 }, (_, index) => ((0, jsx_runtime_1.jsx)(react_1.Skeleton, { className: "h-80 rounded-xl" }, index))) }));
109
110
  }
110
111
  if (!session?.user) {
111
112
  return (0, jsx_runtime_1.jsx)("p", { className: "text-sm text-muted", children: "Sign in to see your plan options." });
@@ -113,7 +114,7 @@ function ConnectPlans({ planIds, hideCoupon = false, className }) {
113
114
  if (error) {
114
115
  return (0, jsx_runtime_1.jsx)("p", { className: "text-sm text-danger", children: "Could not load plans." });
115
116
  }
116
- return ((0, jsx_runtime_1.jsx)(motion_js_1.MotionProvider, { children: (0, jsx_runtime_1.jsxs)("div", { className: `flex flex-col gap-6 ${className ?? ""}`, children: [(0, jsx_runtime_1.jsxs)("div", { className: "flex flex-wrap items-center justify-between gap-4", children: [hasAnnual && hasMonthly && ((0, jsx_runtime_1.jsxs)("div", { className: "flex items-center gap-3", children: [(0, jsx_runtime_1.jsx)("span", { className: showAnnual ? "text-sm text-muted" : "text-sm font-medium text-foreground", children: "Monthly" }), (0, jsx_runtime_1.jsx)(react_1.Switch, { "aria-label": "Bill annually", isSelected: showAnnual, onChange: setAnnual, children: (0, jsx_runtime_1.jsx)(react_1.Switch.Content, { children: (0, jsx_runtime_1.jsx)(react_1.Switch.Control, { children: (0, jsx_runtime_1.jsx)(react_1.Switch.Thumb, {}) }) }) }), (0, jsx_runtime_1.jsx)("span", { className: showAnnual ? "text-sm font-medium text-foreground" : "text-sm text-muted", children: "Annual" }), bestSaving !== null && ((0, jsx_runtime_1.jsxs)(react_1.Chip, { className: "bg-accent-soft text-accent", children: ["Save up to ", bestSaving, "%"] }))] })), !hideCoupon && ((0, jsx_runtime_1.jsxs)("div", { className: "flex items-center gap-2", children: [(0, jsx_runtime_1.jsx)(lucide_react_1.Tag, { className: "size-4 text-muted", "aria-hidden": "true" }), (0, jsx_runtime_1.jsx)(react_1.Input, { "aria-label": "Coupon code", placeholder: "Coupon code", value: coupon, onChange: (event) => setCoupon(event.target.value.toUpperCase()), className: "w-40" })] }))] }), subscription?.subscription?.appliedDiscount && ((0, jsx_runtime_1.jsxs)("div", { className: "flex items-center gap-2 rounded-lg bg-accent-soft px-3 py-2 text-sm text-accent", children: [(0, jsx_runtime_1.jsx)(lucide_react_1.Sparkles, { className: "size-4 shrink-0", "aria-hidden": "true" }), (0, jsx_runtime_1.jsxs)("span", { children: [(0, plan_pricing_js_1.discountLabel)(subscription.subscription.appliedDiscount), subscription.subscription.appliedDiscount.code
117
+ return ((0, jsx_runtime_1.jsx)(motion_js_1.MotionProvider, { children: (0, jsx_runtime_1.jsxs)("div", { className: (0, cn_js_1.cn)("flex flex-col gap-6", className), children: [(0, jsx_runtime_1.jsxs)("div", { className: "flex flex-wrap items-center justify-between gap-4", children: [hasAnnual && hasMonthly && ((0, jsx_runtime_1.jsxs)("div", { className: "flex items-center gap-3", children: [(0, jsx_runtime_1.jsx)("span", { className: showAnnual ? "text-sm text-muted" : "text-sm font-medium text-foreground", children: "Monthly" }), (0, jsx_runtime_1.jsx)(react_1.Switch, { "aria-label": "Bill annually", isSelected: showAnnual, onChange: setAnnual, children: (0, jsx_runtime_1.jsx)(react_1.Switch.Content, { children: (0, jsx_runtime_1.jsx)(react_1.Switch.Control, { children: (0, jsx_runtime_1.jsx)(react_1.Switch.Thumb, {}) }) }) }), (0, jsx_runtime_1.jsx)("span", { className: showAnnual ? "text-sm font-medium text-foreground" : "text-sm text-muted", children: "Annual" }), bestSaving !== null && ((0, jsx_runtime_1.jsxs)(react_1.Chip, { className: "bg-accent-soft text-accent", children: ["Save up to ", bestSaving, "%"] }))] })), !hideCoupon && ((0, jsx_runtime_1.jsxs)("div", { className: "flex items-center gap-2", children: [(0, jsx_runtime_1.jsx)(lucide_react_1.Tag, { className: "size-4 text-muted", "aria-hidden": "true" }), (0, jsx_runtime_1.jsx)(react_1.Input, { "aria-label": "Coupon code", placeholder: "Coupon code", value: coupon, onChange: (event) => setCoupon(event.target.value.toUpperCase()), className: "w-40" })] }))] }), subscription?.subscription?.appliedDiscount && ((0, jsx_runtime_1.jsxs)("div", { className: "flex items-center gap-2 rounded-lg bg-accent-soft px-3 py-2 text-sm text-accent", children: [(0, jsx_runtime_1.jsx)(lucide_react_1.Sparkles, { className: "size-4 shrink-0", "aria-hidden": "true" }), (0, jsx_runtime_1.jsxs)("span", { children: [(0, plan_pricing_js_1.discountLabel)(subscription.subscription.appliedDiscount), subscription.subscription.appliedDiscount.code
117
118
  ? ` - code ${subscription.subscription.appliedDiscount.code}`
118
119
  : ""] })] })), (0, jsx_runtime_1.jsx)(ConnectCheckoutStatusRow, { checkout: checkout }), (0, jsx_runtime_1.jsx)(motion_js_1.m.div, { className: "grid gap-4 sm:grid-cols-2 lg:grid-cols-3", variants: { show: { transition: { staggerChildren: 0.06 } } }, initial: "hidden", animate: "show", children: tiers.map((tier) => {
119
120
  // The price point this card is showing. A tier missing the toggled
@@ -9,6 +9,7 @@ const lucide_react_1 = require("lucide-react");
9
9
  const helpers_js_1 = require("../../helpers.js");
10
10
  const provider_js_1 = require("../provider.js");
11
11
  const motion_js_1 = require("./motion.js");
12
+ const cn_js_1 = require("../../cn.js");
12
13
  /**
13
14
  * Codes that mean "somebody at Connect is looking at it" - there is no action
14
15
  * for the customer to take, so these get an informational treatment and no
@@ -61,14 +62,14 @@ function ConnectReadinessBanner({ includeAdvisory = false, hideWhenReady = true,
61
62
  if (!session?.user)
62
63
  return null;
63
64
  if (isLoading) {
64
- return ((0, jsx_runtime_1.jsxs)("div", { className: `flex items-center gap-2 text-sm text-muted ${className ?? ""}`, children: [(0, jsx_runtime_1.jsx)(lucide_react_1.Loader2, { className: "size-4 animate-spin", "aria-hidden": "true" }), "Checking your workspace\u2026"] }));
65
+ return ((0, jsx_runtime_1.jsxs)("div", { className: (0, cn_js_1.cn)("flex items-center gap-2 text-sm text-muted", className), children: [(0, jsx_runtime_1.jsx)(lucide_react_1.Loader2, { className: "size-4 animate-spin", "aria-hidden": "true" }), "Checking your workspace\u2026"] }));
65
66
  }
66
67
  if (!data)
67
68
  return null;
68
69
  const blockers = includeAdvisory ? data.readiness.readiness.blockers : (0, helpers_js_1.blockingBlockers)(data.readiness);
69
70
  if (blockers.length === 0)
70
71
  return hideWhenReady ? null : (0, jsx_runtime_1.jsx)(ReadyRow, { className: className });
71
- return ((0, jsx_runtime_1.jsx)(motion_js_1.MotionProvider, { children: (0, jsx_runtime_1.jsx)(motion_js_1.m.div, { className: `flex flex-col gap-2 ${className ?? ""}`, role: "status", initial: { opacity: 0, y: -6 }, animate: { opacity: 1, y: 0 }, transition: motion_js_1.ENTER, children: blockers.map((blocker) => ((0, jsx_runtime_1.jsx)(BlockerRow, { blocker: blocker }, `${blocker.code}-${blocker.message}`))) }) }));
72
+ return ((0, jsx_runtime_1.jsx)(motion_js_1.MotionProvider, { children: (0, jsx_runtime_1.jsx)(motion_js_1.m.div, { className: (0, cn_js_1.cn)("flex flex-col gap-2", className), role: "status", initial: { opacity: 0, y: -6 }, animate: { opacity: 1, y: 0 }, transition: motion_js_1.ENTER, children: blockers.map((blocker) => ((0, jsx_runtime_1.jsx)(BlockerRow, { blocker: blocker }, `${blocker.code}-${blocker.message}`))) }) }));
72
73
  }
73
74
  function BlockerRow({ blocker }) {
74
75
  const inProgress = IN_PROGRESS_CODES.has(blocker.code);
@@ -76,7 +77,7 @@ function BlockerRow({ blocker }) {
76
77
  return ((0, jsx_runtime_1.jsxs)("div", { className: `flex flex-wrap items-center justify-between gap-3 rounded-lg px-3 py-2 text-sm ${inProgress || !isBlocking ? "bg-surface text-muted" : "bg-danger-soft text-danger"}`, children: [(0, jsx_runtime_1.jsxs)("span", { className: "flex items-center gap-2", children: [inProgress || !isBlocking ? ((0, jsx_runtime_1.jsx)(lucide_react_1.Info, { className: "size-4 shrink-0", "aria-hidden": "true" })) : ((0, jsx_runtime_1.jsx)(lucide_react_1.AlertTriangle, { className: "size-4 shrink-0", "aria-hidden": "true" })), blocker.message] }), blocker.resolveUrl && !inProgress && ((0, jsx_runtime_1.jsxs)(react_1.Button, { variant: "secondary", onPress: () => window.open(blocker.resolveUrl, "_blank", "noopener,noreferrer"), children: [actionLabel(blocker), (0, jsx_runtime_1.jsx)(lucide_react_1.ExternalLink, { className: "size-3.5" })] }))] }));
77
78
  }
78
79
  function ReadyRow({ className }) {
79
- return ((0, jsx_runtime_1.jsx)("p", { className: `text-sm text-muted ${className ?? ""}`, children: "This workspace is cleared to use every part of the application." }));
80
+ return ((0, jsx_runtime_1.jsx)("p", { className: (0, cn_js_1.cn)("text-sm text-muted", className), children: "This workspace is cleared to use every part of the application." }));
80
81
  }
81
82
  /**
82
83
  * Renders `children` only once the workspace clears every blocking gate.
@@ -9,6 +9,7 @@ exports.SidebarLabel = SidebarLabel;
9
9
  const jsx_runtime_1 = require("react/jsx-runtime");
10
10
  const react_1 = require("react");
11
11
  const motion_js_1 = require("./motion.js");
12
+ const cn_js_1 = require("../../cn.js");
12
13
  /** A plain anchor - correct anywhere, and a full page load. See `ConnectLinkRenderer`. */
13
14
  const defaultRenderLink = ({ children, ...props }) => (0, jsx_runtime_1.jsx)("a", { ...props, children: children });
14
15
  exports.defaultRenderLink = defaultRenderLink;
@@ -145,5 +146,5 @@ function useConnectShell() {
145
146
  */
146
147
  function SidebarLabel({ children, className, }) {
147
148
  const { collapsed } = useConnectShell();
148
- return ((0, jsx_runtime_1.jsx)(motion_js_1.m.span, { className: `min-w-0 flex-1 truncate text-start ${className ?? ""}`, animate: { opacity: collapsed ? 0 : 1 }, transition: collapsed ? { duration: 0.1 } : { duration: 0.18, delay: 0.1 }, "aria-hidden": collapsed, children: children }));
149
+ return ((0, jsx_runtime_1.jsx)(motion_js_1.m.span, { className: (0, cn_js_1.cn)("min-w-0 flex-1 truncate text-start", className), animate: { opacity: collapsed ? 0 : 1 }, transition: collapsed ? { duration: 0.1 } : { duration: 0.18, delay: 0.1 }, "aria-hidden": collapsed, children: children }));
149
150
  }
@@ -10,6 +10,7 @@ const motion_js_1 = require("./motion.js");
10
10
  const shell_context_js_1 = require("./shell-context.js");
11
11
  Object.defineProperty(exports, "defaultRenderLink", { enumerable: true, get: function () { return shell_context_js_1.defaultRenderLink; } });
12
12
  const use_active_path_js_1 = require("../use-active-path.js");
13
+ const cn_js_1 = require("../../cn.js");
13
14
  const nav_js_1 = require("./nav.js");
14
15
  function rowClass(isActive, depth) {
15
16
  // The indent is a *margin*, not padding, so a child row steps in while the
@@ -68,7 +69,7 @@ function ConnectSidebarNav({ nav, activePath, renderLink, collapsed, onNavigate,
68
69
  const pathname = (0, use_active_path_js_1.useActivePath)(activePath ?? shell.activePath);
69
70
  const active = (0, nav_js_1.resolveActivePath)(pathname, nav);
70
71
  const sections = (0, nav_js_1.normalizeNav)(nav);
71
- return ((0, jsx_runtime_1.jsx)("div", { className: `flex flex-col gap-2 ${className ?? ""}`, children: sections.map((section, index) => ((0, jsx_runtime_1.jsxs)("nav", { "aria-label": section.title ?? "Main", className: "flex flex-col gap-1", children: [section.title && (0, jsx_runtime_1.jsx)(SectionHeading, { title: section.title, collapsed: isCollapsed }), section.items.map((item) => {
72
+ return ((0, jsx_runtime_1.jsx)("div", { className: (0, cn_js_1.cn)("flex flex-col gap-2", className), children: sections.map((section, index) => ((0, jsx_runtime_1.jsxs)("nav", { "aria-label": section.title ?? "Main", className: "flex flex-col gap-1", children: [section.title && (0, jsx_runtime_1.jsx)(SectionHeading, { title: section.title, collapsed: isCollapsed }), section.items.map((item) => {
72
73
  const isActive = (0, nav_js_1.isNavItemActive)(item, active);
73
74
  const activeChild = (item.items ?? []).some((child) => (0, nav_js_1.isNavItemActive)(child, active));
74
75
  return ((0, jsx_runtime_1.jsxs)("div", { className: "flex w-full flex-col gap-1", children: [(0, jsx_runtime_1.jsx)(NavRow, { item: item, isActive: isActive, hasActiveChild: activeChild, collapsed: isCollapsed, depth: 0, renderLink: link, onNavigate: navigate }), !isCollapsed &&