@hanzo/rooms 0.1.50 → 0.1.51

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 (3) hide show
  1. package/dist/Me.js +24 -6
  2. package/dist/Orgs.js +17 -6
  3. package/package.json +1 -1
package/dist/Me.js CHANGED
@@ -24,7 +24,7 @@ import { Fragment as _Fragment, jsx as _jsx, jsxs as _jsxs } from "react/jsx-run
24
24
  // `POST /v1/account/orgs` the no-organization gate uses (components/workspace/
25
25
  // Orgs.tsx); the next load mints a token that carries it.
26
26
  import { useEffect, useMemo, useRef, useState } from 'react';
27
- import { Popover, SizableText, View, XStack, YStack } from '@hanzo/gui';
27
+ import { Popover, SizableText, usePopperContextSlow, View, XStack, YStack } from '@hanzo/gui';
28
28
  import { Building2, ChevronsUpDown, CreditCard, ExternalLink, Gauge, KeyRound, LifeBuoy, LogOut, Plus, Settings, Sparkles, Users, X } from 'lucide-react';
29
29
  import { label, Meter, path, said as spoken, useStanding, useWho } from '@hanzo/build';
30
30
  import { useOrganizations } from '@hanzo/iam/react';
@@ -132,6 +132,24 @@ function rove(e, close) {
132
132
  e.preventDefault();
133
133
  rows[to]?.focus();
134
134
  }
135
+ /**
136
+ * The menu grows once the plan is read (`Meter`), after it was placed, and the
137
+ * popover follows its anchor, not its own size. So it is placed again whenever
138
+ * its content changes size, and never hangs over the row it opens from.
139
+ */
140
+ function Refit({ children }) {
141
+ const { update } = usePopperContextSlow();
142
+ const box = useRef(null);
143
+ useEffect(() => {
144
+ const node = box.current;
145
+ if (!node || typeof ResizeObserver === 'undefined')
146
+ return;
147
+ const watch = new ResizeObserver(() => update());
148
+ watch.observe(node);
149
+ return () => watch.disconnect();
150
+ }, [update]);
151
+ return (_jsx(View, { render: _jsx("div", { ref: box }), self: "stretch", minW: 0, children: children }));
152
+ }
135
153
  export function Me({ host, narrow = false, onOrg, }) {
136
154
  const menu = useWho();
137
155
  const [creating, setCreating] = useState(false);
@@ -168,11 +186,11 @@ export function Me({ host, narrow = false, onOrg, }) {
168
186
  return null;
169
187
  return (_jsxs(_Fragment, { children: [_jsxs(Popover, { open: menu.open, onOpenChange: menu.onOpenChange, placement: narrow ? 'right-end' : 'top-start', allowFlip: true, stayInFrame: true, offset: 8, children: [_jsx(Popover.Anchor, { width: "100%", children: _jsxs(XStack, { ref: (el) => {
170
188
  row.current = el instanceof HTMLElement ? el : null;
171
- }, render: "button", "data-slot": "me", "aria-label": `Account: ${[who, current].filter(Boolean).join(' · ')}`, "aria-haspopup": "menu", "aria-expanded": menu.open, onPress: menu.toggle, width: narrow ? 40 : '100%', minH: narrow ? 40 : 48, px: narrow ? 0 : '$2', gap: "$2.5", rounded: narrow ? 999 : '$5', items: "center", justify: narrow ? 'center' : 'flex-start', hoverStyle: { bg: '$hover' }, focusVisibleStyle: { outlineWidth: 2, outlineStyle: 'solid', outlineColor: '$outlineColor' }, cursor: "pointer", children: [_jsx(Face, { host: host, size: narrow ? 30 : 32 }), narrow ? null : (_jsxs(_Fragment, { children: [_jsxs(YStack, { flex: 1, minW: 0, children: [_jsx(SizableText, { size: "$3", fontWeight: "600", color: "$ink", numberOfLines: 1, text: "left", children: who }), standing ? (_jsx(SizableText, { size: "$1", color: "$soft", numberOfLines: 1, text: "left", children: standing })) : null] }), _jsx(ChevronsUpDown, { size: 14, "aria-hidden": true, opacity: 0.6 })] }))] }) }), _jsx(Popover.Content, { ...glass(2), items: "stretch", borderWidth: 1, rounded: "$6", p: "$1.5", width: 288, maxW: "calc(100vw - 16px)", maxH: "calc(100dvh - 32px)", overflowY: "auto", overflowX: "hidden", children: _jsx(View, { render: _jsx("div", { ref: land, role: "menu", "aria-label": "Account", onKeyDown: (e) => rove(e, close) }), self: "stretch", minW: 0, children: _jsxs(YStack, { gap: 2, children: [_jsx(SizableText, { size: "$2", color: "$soft", px: "$2", py: "$1.5", numberOfLines: 1, children: host.person.email || who }), _jsx(Rule, {}), _jsx(Meter, { read: read, onPlans: () => go(path({ kind: 'screen', screen: 'plans' })), onBilling: () => go(path({ kind: 'settings', section: 'billing' })) }), _jsx(Rule, {}), _jsx(Label, { children: "Organizations" }), _jsx(YStack, { role: "group", "aria-label": "Organizations", gap: 2, children: all.map((o) => (_jsx(Item, { icon: _jsx(Mark, { org: o }), label: o.display, sub: [o.personal ? 'Personal' : said(o.role), planFor(o)].filter(Boolean).join(' · '), checked: o.name === current, onPress: () => {
172
- menu.onOpenChange(false);
173
- if (o.name !== current)
174
- onOrg(o.name);
175
- } }, o.name))) }), _jsx(Item, { icon: _jsx(Plus, { size: 15, "aria-hidden": true }), label: "Create organization", onPress: () => (menu.onOpenChange(false), setCreating(true)) }), _jsx(Item, { icon: _jsx(Settings, { size: 15, "aria-hidden": true }), label: "Organization settings", onPress: () => (menu.onOpenChange(false), window.location.assign(site(ORG_SETTINGS))) }), where().team ? null : here && !here.personal ? (_jsx(Item, { icon: _jsx(Users, { size: 15, "aria-hidden": true }), label: "Open in Hanzo Team", trail: _jsx(ExternalLink, { size: 13, "aria-hidden": true, opacity: 0.6 }), onPress: () => (menu.onOpenChange(false), window.location.assign(`${ENTRY}&org=${encodeURIComponent(here.name)}`)) })) : (_jsx(Item, { icon: _jsx(Users, { size: 15, "aria-hidden": true }), label: "Create a team", onPress: () => (menu.onOpenChange(false), setCreating(true)) })), superAdmin() ? (_jsx(Item, { icon: _jsx(Building2, { size: 15, "aria-hidden": true }), label: "All organizations", onPress: () => (menu.onOpenChange(false), setSupporting(true)) })) : null, _jsx(Rule, {}), _jsx(Item, { icon: _jsx(Settings, { size: 15, "aria-hidden": true }), label: "Settings", onPress: () => go(path({ kind: 'settings', section: 'general' })) }), _jsx(Item, { icon: _jsx(Gauge, { size: 15, "aria-hidden": true }), label: "Usage", onPress: () => go(path({ kind: 'settings', section: 'usage' })) }), _jsx(Item, { icon: _jsx(CreditCard, { size: 15, "aria-hidden": true }), label: "Billing", onPress: () => go(path({ kind: 'settings', section: 'billing' })) }), _jsx(Item, { icon: _jsx(KeyRound, { size: 15, "aria-hidden": true }), label: "API keys", onPress: () => (menu.onOpenChange(false), window.location.assign(KEYS)) }), _jsx(Item, { icon: _jsx(Sparkles, { size: 15, "aria-hidden": true }), label: "View all plans", onPress: () => go(path({ kind: 'screen', screen: 'plans' })) }), _jsx(Item, { icon: _jsx(LifeBuoy, { size: 15, "aria-hidden": true }), label: "Get help", onPress: () => (menu.onOpenChange(false), window.open(DOCS, '_blank', 'noopener,noreferrer')) }), host.signOut ? (_jsxs(_Fragment, { children: [_jsx(Rule, {}), _jsx(Item, { icon: _jsx(LogOut, { size: 15, "aria-hidden": true }), label: "Log out", onPress: () => (menu.onOpenChange(false), host.signOut?.()) })] })) : null] }) }) })] }), _jsx(Create, { open: creating, onOpenChange: setCreating }), supporting ? _jsx(SupportPicker, { onClose: () => setSupporting(false) }) : null] }));
189
+ }, render: "button", "data-slot": "me", "aria-label": `Account: ${[who, current].filter(Boolean).join(' · ')}`, "aria-haspopup": "menu", "aria-expanded": menu.open, onPress: menu.toggle, width: narrow ? 40 : '100%', minH: narrow ? 40 : 48, px: narrow ? 0 : '$2', gap: "$2.5", rounded: narrow ? 999 : '$5', items: "center", justify: narrow ? 'center' : 'flex-start', hoverStyle: { bg: '$hover' }, focusVisibleStyle: { outlineWidth: 2, outlineStyle: 'solid', outlineColor: '$outlineColor' }, cursor: "pointer", children: [_jsx(Face, { host: host, size: narrow ? 30 : 32 }), narrow ? null : (_jsxs(_Fragment, { children: [_jsxs(YStack, { flex: 1, minW: 0, children: [_jsx(SizableText, { size: "$3", fontWeight: "600", color: "$ink", numberOfLines: 1, text: "left", children: who }), standing ? (_jsx(SizableText, { size: "$1", color: "$soft", numberOfLines: 1, text: "left", children: standing })) : null] }), _jsx(ChevronsUpDown, { size: 14, "aria-hidden": true, opacity: 0.6 })] }))] }) }), _jsx(Popover.Content, { ...glass(2), items: "stretch", borderWidth: 1, rounded: "$6", p: "$1.5", width: 288, maxW: "calc(100vw - 16px)", maxH: "calc(100dvh - 32px)", overflowY: "auto", overflowX: "hidden", children: _jsx(Refit, { children: _jsx(View, { render: _jsx("div", { ref: land, role: "menu", "aria-label": "Account", onKeyDown: (e) => rove(e, close) }), self: "stretch", minW: 0, children: _jsxs(YStack, { gap: 2, children: [_jsx(SizableText, { size: "$2", color: "$soft", px: "$2", py: "$1.5", numberOfLines: 1, children: host.person.email || who }), _jsx(Rule, {}), _jsx(Meter, { read: read, onPlans: () => go(path({ kind: 'screen', screen: 'plans' })), onBilling: () => go(path({ kind: 'settings', section: 'billing' })) }), _jsx(Rule, {}), _jsx(Label, { children: "Organizations" }), _jsx(YStack, { role: "group", "aria-label": "Organizations", gap: 2, children: all.map((o) => (_jsx(Item, { icon: _jsx(Mark, { org: o }), label: o.display, sub: [o.personal ? 'Personal' : said(o.role), planFor(o)].filter(Boolean).join(' · '), checked: o.name === current, onPress: () => {
190
+ menu.onOpenChange(false);
191
+ if (o.name !== current)
192
+ onOrg(o.name);
193
+ } }, o.name))) }), _jsx(Item, { icon: _jsx(Plus, { size: 15, "aria-hidden": true }), label: "Create organization", onPress: () => (menu.onOpenChange(false), setCreating(true)) }), _jsx(Item, { icon: _jsx(Settings, { size: 15, "aria-hidden": true }), label: "Organization settings", onPress: () => (menu.onOpenChange(false), window.location.assign(site(ORG_SETTINGS))) }), where().team ? null : here && !here.personal ? (_jsx(Item, { icon: _jsx(Users, { size: 15, "aria-hidden": true }), label: "Open in Hanzo Team", trail: _jsx(ExternalLink, { size: 13, "aria-hidden": true, opacity: 0.6 }), onPress: () => (menu.onOpenChange(false), window.location.assign(`${ENTRY}&org=${encodeURIComponent(here.name)}`)) })) : (_jsx(Item, { icon: _jsx(Users, { size: 15, "aria-hidden": true }), label: "Create a team", onPress: () => (menu.onOpenChange(false), setCreating(true)) })), superAdmin() ? (_jsx(Item, { icon: _jsx(Building2, { size: 15, "aria-hidden": true }), label: "All organizations", onPress: () => (menu.onOpenChange(false), setSupporting(true)) })) : null, _jsx(Rule, {}), _jsx(Item, { icon: _jsx(Settings, { size: 15, "aria-hidden": true }), label: "Settings", onPress: () => go(path({ kind: 'settings', section: 'general' })) }), _jsx(Item, { icon: _jsx(Gauge, { size: 15, "aria-hidden": true }), label: "Usage", onPress: () => go(path({ kind: 'settings', section: 'usage' })) }), _jsx(Item, { icon: _jsx(CreditCard, { size: 15, "aria-hidden": true }), label: "Billing", onPress: () => go(path({ kind: 'settings', section: 'billing' })) }), _jsx(Item, { icon: _jsx(KeyRound, { size: 15, "aria-hidden": true }), label: "API keys", onPress: () => (menu.onOpenChange(false), window.location.assign(KEYS)) }), _jsx(Item, { icon: _jsx(Sparkles, { size: 15, "aria-hidden": true }), label: "View all plans", onPress: () => go(path({ kind: 'screen', screen: 'plans' })) }), _jsx(Item, { icon: _jsx(LifeBuoy, { size: 15, "aria-hidden": true }), label: "Get help", onPress: () => (menu.onOpenChange(false), window.open(DOCS, '_blank', 'noopener,noreferrer')) }), host.signOut ? (_jsxs(_Fragment, { children: [_jsx(Rule, {}), _jsx(Item, { icon: _jsx(LogOut, { size: 15, "aria-hidden": true }), label: "Log out", onPress: () => (menu.onOpenChange(false), host.signOut?.()) })] })) : null] }) }) }) })] }), _jsx(Create, { open: creating, onOpenChange: setCreating }), supporting ? _jsx(SupportPicker, { onClose: () => setSupporting(false) }) : null] }));
176
194
  }
177
195
  /**
178
196
  * The sheet that creates an organization: a name, then `POST /v1/account/orgs`,
package/dist/Orgs.js CHANGED
@@ -175,12 +175,13 @@ export function Orgs({ children, front = false, forward, signup = false, }) {
175
175
  if (!hydrated)
176
176
  return front && Landing ? _jsx(Landing, {}) : _jsx(Veil, {});
177
177
  // Past hydration the room keeps its place in the tree in every branch that
178
- // draws it — the veil goes AFTER it — so a token refresh mid-session never
178
+ // draws it — `Held`, the veil AFTER it — so a token refresh mid-session never
179
179
  // remounts what the reader had open. The veil is unconditional now: it was
180
180
  // hung only when a token was already in hand, which left a visitor arriving
181
- // without one looking at the room while the SDK decided.
181
+ // without one looking at the room while the SDK decided. Under it the room is
182
+ // inert: it takes no key or press until the gate has decided.
182
183
  if (isLoading)
183
- return front && Landing && !hasSession() && !arriving ? _jsx(Landing, {}) : _jsxs(_Fragment, { children: [children, _jsx(Veil, {})] });
184
+ return front && Landing && !hasSession() && !arriving ? _jsx(Landing, {}) : _jsxs(_Fragment, { children: [_jsx(Held, { veiled: true, children: children }), _jsx(Veil, {})] });
184
185
  if (arriving && !isAuthenticated)
185
186
  return _jsx(SignIn, { to: LOGIN });
186
187
  if (!isAuthenticated)
@@ -202,11 +203,11 @@ export function Orgs({ children, front = false, forward, signup = false, }) {
202
203
  // exception rather than the fallback. The one exception to the plan is a
203
204
  // personal organization on Free, off hanzo.team, and its kind is read first.
204
205
  if (!tier && !wrong)
205
- return _jsxs(_Fragment, { children: [children, _jsx(Veil, {})] });
206
+ return _jsxs(_Fragment, { children: [_jsx(Held, { veiled: true, children: children }), _jsx(Veil, {})] });
206
207
  if (free && personal === null)
207
- return _jsxs(_Fragment, { children: [children, _jsx(Veil, {})] });
208
+ return _jsxs(_Fragment, { children: [_jsx(Held, { veiled: true, children: children }), _jsx(Veil, {})] });
208
209
  if (open)
209
- return forward ? _jsx(Forward, { to: forward }) : _jsx(_Fragment, { children: children });
210
+ return forward ? _jsx(Forward, { to: forward }) : _jsx(_Fragment, { children: _jsx(Held, { veiled: false, children: children }) });
210
211
  const swap = () => setPicked(false);
211
212
  if (door) {
212
213
  // A billing OUTAGE is ours; the plan is still the condition.
@@ -293,6 +294,16 @@ function Heading({ title, lede, lead }) {
293
294
  function Veil() {
294
295
  return _jsx(View, { "aria-hidden": true, position: "fixed", inset: 0, z: SHEET, bg: "var(--background)" });
295
296
  }
297
+ /**
298
+ * The room, in the one place every branch that draws it puts it. Under the veil
299
+ * it is inert and hidden from assistive technology, so what the veil covers
300
+ * takes no focus, key or press: Dev's composer focuses itself as it mounts, and
301
+ * a team on Free could otherwise type and send before its ask was drawn. It
302
+ * lays out as its children do.
303
+ */
304
+ function Held({ veiled, children }) {
305
+ return (_jsx(View, { render: _jsx("div", { "data-slot": "held", inert: veiled, "aria-hidden": veiled || undefined }), display: "contents", children: children }));
306
+ }
296
307
  /** An open organization, sent on. */
297
308
  function Forward({ to }) {
298
309
  const { router } = useRooms();
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@hanzo/rooms",
3
- "version": "0.1.50",
3
+ "version": "0.1.51",
4
4
  "description": "The workspace rooms — Home, Chat, Inbox, Contacts, Meet, Cal, Drive, Board, Work, Bots — and the frame that holds them, once, for every Hanzo surface.",
5
5
  "type": "module",
6
6
  "main": "./dist/index.js",