@hanzo/rooms 0.1.96 → 0.1.98

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/Recents.js CHANGED
@@ -73,7 +73,9 @@ function List({ mode, rows, view, keep, open, reading, error, onOpen, threads, h
73
73
  // shows on hover and focus, a run's state is a dot at its end.
74
74
  const row = (r) => {
75
75
  const t = threads?.get(r.id);
76
- const item = (_jsx(SidebarItem, { "data-kind": r.kind, active: open(r), rounded: "$4", minH: 31, px: "$2", pr: t || r.kind === 'dev' ? '$7' : '$2', ...(t ? { $touchable: { pr: 52 } } : null), onKeyDown: (e) => {
76
+ const item = (_jsx(SidebarItem, { "data-kind": r.kind, active: open(r), rounded: "$4", minH: 31, px: "$2", pr: t || r.kind === 'dev' ? '$7' : '$2',
77
+ // A thumb's 44 on a touch screen; a conversation's ⋯ is a thumb wide there and always there: the title stops short of it.
78
+ $touchable: t ? { minH: 44, pr: 52 } : { minH: 44 }, onKeyDown: (e) => {
77
79
  if (e.key !== 'Enter' && e.key !== ' ')
78
80
  return;
79
81
  e.preventDefault?.();
@@ -98,5 +100,5 @@ function List({ mode, rows, view, keep, open, reading, error, onOpen, threads, h
98
100
  : pinned
99
101
  ? null
100
102
  : note;
101
- return (_jsx(YStack, { "data-slot": "recents", flex: 1, minH: 0, children: _jsxs(ScrollView, { flex: 1, showsVerticalScrollIndicator: false, children: [error ? _jsx(Note, { children: error.message }) : null, _jsxs(YStack, { role: "list", "aria-labelledby": label, gap: 1, children: [pinned ? (_jsxs(YStack, { role: "listitem", gap: 1, children: [heading(PINNED), menus(_jsx(YStack, { role: "list", "aria-label": PINNED, gap: 1, children: pinned.rows.map(row) }))] })) : null, _jsxs(YStack, { role: "listitem", gap: 1, children: [heading('Recents', label, true), loose.length ? menus(rest) : rest] })] })] }) }));
103
+ return (_jsx(YStack, { "data-slot": "recents", flex: 1, minH: 0, children: _jsxs(ScrollView, { flex: 1, showsVerticalScrollIndicator: false, ...{ overscrollBehavior: 'contain' }, children: [error ? _jsx(Note, { children: error.message }) : null, _jsxs(YStack, { role: "list", "aria-labelledby": label, gap: 1, children: [pinned ? (_jsxs(YStack, { role: "listitem", gap: 1, children: [heading(PINNED), menus(_jsx(YStack, { role: "list", "aria-label": PINNED, gap: 1, children: pinned.rows.map(row) }))] })) : null, _jsxs(YStack, { role: "listitem", gap: 1, children: [heading('Recents', label, true), loose.length ? menus(rest) : rest] })] })] }) }));
102
104
  }
package/dist/Settings.js CHANGED
@@ -23,6 +23,7 @@ import { Fragment as _Fragment, jsx as _jsx, jsxs as _jsxs } from "react/jsx-run
23
23
  //
24
24
  // The panes that are not yet built say so in one sentence rather than pretending
25
25
  // to a screen. An honest empty pane is a smaller lie than an invented one.
26
+ import { useI18n } from '@hanzo/i18n/react';
26
27
  import { app, dev } from './lib/host.js';
27
28
  import { ArrowLeft, ArrowUpRight } from 'lucide-react';
28
29
  import { Text } from '@hanzo/ui';
@@ -104,11 +105,12 @@ export const FIRST = ROWS[0].id;
104
105
  * the address never moves.
105
106
  */
106
107
  export function SettingsList({ active, filter, onPick, onBack, }) {
108
+ const { t } = useI18n();
107
109
  const q = filter.trim().toLowerCase();
108
110
  const groups = q
109
111
  ? GROUPS.map((g) => ({ ...g, rows: g.rows.filter((r) => r.label.toLowerCase().includes(q)) })).filter((g) => g.rows.length > 0)
110
112
  : GROUPS;
111
- return (_jsxs(_Fragment, { children: [_jsx(SidebarItem, { icon: _jsx(ArrowLeft, { size: 15, "aria-hidden": true }), onPress: onBack, children: "Workspace" }), q && groups.length === 0 ? (_jsxs(Text, { px: "$3", py: "$2", fontSize: "$2", color: "$quiet", children: ["No matches for \u201C", filter.trim(), "\u201D."] })) : null, groups.map((g) => (_jsx(SidebarSection, { label: g.label, children: g.rows.map((r) => (_jsx(SidebarItem, {
113
+ return (_jsxs(_Fragment, { children: [_jsx(SidebarItem, { icon: _jsx(ArrowLeft, { size: 15, "aria-hidden": true }), onPress: onBack, children: t("Workspace") }), q && groups.length === 0 ? (_jsxs(Text, { px: "$3", py: "$2", fontSize: "$2", color: "$quiet", children: [t("No matches for “"), filter.trim(), "\u201D."] })) : null, groups.map((g) => (_jsx(SidebarSection, { label: g.label, children: g.rows.map((r) => (_jsx(SidebarItem, {
112
114
  // The arrow says this row LEAVES. Without it a row that navigates
113
115
  // away and one that draws a pane in place look identical until
114
116
  // pressed.
package/dist/Top.js CHANGED
@@ -16,6 +16,7 @@ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
16
16
  // was last). In Studio neither is lit. New is Chat's New chat, and elsewhere the
17
17
  // mode's own New row (@hanzo/build's and @hanzo/studio's `nav`, `NEW`), so the
18
18
  // bar names it as the sidebar does.
19
+ import { useI18n } from '@hanzo/i18n/react';
19
20
  import { SquarePen } from 'lucide-react';
20
21
  import { Text, XStack } from '@hanzo/gui';
21
22
  import { nav, NEW } from '@hanzo/build';
@@ -33,7 +34,8 @@ const SEGMENT = 73;
33
34
  const GLASS = { ...pane(), borderColor: 'var(--border-strong)', borderRadius: 9999 };
34
35
  /** A round glass button named for what it does. */
35
36
  function Round({ label, onPress, children, ...rest }) {
36
- return (_jsx(XStack, { render: "button", "aria-label": label, onPress: onPress, ...GLASS, width: ROUND, height: ROUND, items: "center", justify: "center", shrink: 0, cursor: "pointer", pressStyle: { bg: '$raised' }, focusVisibleStyle: { outlineWidth: 2, outlineStyle: 'solid', outlineColor: '$outlineColor' }, ...rest, children: children }));
37
+ const { t } = useI18n();
38
+ return (_jsx(XStack, { render: "button", "aria-label": t(label), onPress: onPress, ...GLASS, width: ROUND, height: ROUND, items: "center", justify: "center", shrink: 0, cursor: "pointer", pressStyle: { bg: '$raised' }, focusVisibleStyle: { outlineWidth: 2, outlineStyle: 'solid', outlineColor: '$outlineColor' }, ...rest, children: children }));
37
39
  }
38
40
  /** The drawer's mark: two strokes, the lower one shorter, 19×11 as the reference draws it. */
39
41
  function Lines() {
@@ -44,6 +46,7 @@ function Side({ value, children }) {
44
46
  return (_jsx(ToggleGroupItem, { value: value, size: "lg", width: SEGMENT, rounded: 9999, bg: "transparent", borderColor: "transparent", hoverStyle: { bg: '$hover' }, activeStyle: { bg: '$raised', borderColor: 'transparent' }, children: _jsx(Text, { fontSize: "$4", fontWeight: "500", color: "$ink", numberOfLines: 1, children: children }) }));
45
47
  }
46
48
  export function Top({ host, mode, places, go, onMenu, onChat, onDev, }) {
49
+ const { t } = useI18n();
47
50
  const made = mode === 'chat' ? null : (places ?? nav(host, (p) => host.go(p))).flatMap((g) => g.links).find((l) => l.id === NEW);
48
51
  const fresh = mode === 'chat'
49
52
  ? {
@@ -54,5 +57,5 @@ export function Top({ host, mode, places, go, onMenu, onChat, onDev, }) {
54
57
  },
55
58
  }
56
59
  : { label: made?.label ?? 'New', onPress: made?.onPress };
57
- return (_jsxs(XStack, { "data-slot": "app-bar", height: `calc(${LINE}px + var(--safe-top))`, pt: "var(--safe-top)", px: "$4.5", items: "center", justify: "space-between", shrink: 0, $md: { display: 'none' }, children: [_jsx(Round, { label: "Open sidebar", "data-slot": "bar-menu", onPress: onMenu, children: _jsx(Lines, {}) }), _jsx(XStack, { "data-slot": "bar-mode", ...GLASS, height: LINE, p: "$1", items: "center", children: _jsxs(ToggleGroup, { type: "single", "aria-label": "App", value: mode === 'chat' || mode === 'dev' ? mode : '', disableDeactivation: true, onValueChange: (v) => (v === 'chat' ? onChat() : v === 'dev' ? onDev() : undefined), children: [_jsx(Side, { value: "chat", children: "Chat" }), _jsx(Side, { value: "dev", children: "Dev" })] }) }), _jsx(Round, { label: fresh.label, "data-slot": "bar-new", onPress: fresh.onPress, children: _jsx(SquarePen, { size: 20, "aria-hidden": true }) })] }));
60
+ return (_jsxs(XStack, { "data-slot": "app-bar", height: `calc(${LINE}px + var(--safe-top))`, pt: "var(--safe-top)", px: "$4.5", items: "center", justify: "space-between", shrink: 0, $md: { display: 'none' }, children: [_jsx(Round, { label: t("Open sidebar"), "data-slot": "bar-menu", onPress: onMenu, children: _jsx(Lines, {}) }), _jsx(XStack, { "data-slot": "bar-mode", ...GLASS, height: LINE, p: "$1", items: "center", children: _jsxs(ToggleGroup, { type: "single", "aria-label": t("App"), value: mode === 'chat' || mode === 'dev' ? mode : '', disableDeactivation: true, onValueChange: (v) => (v === 'chat' ? onChat() : v === 'dev' ? onDev() : undefined), children: [_jsx(Side, { value: "chat", children: t("Chat") }), _jsx(Side, { value: "dev", children: t("Dev") })] }) }), _jsx(Round, { label: fresh.label, "data-slot": "bar-new", onPress: fresh.onPress, children: _jsx(SquarePen, { size: 20, "aria-hidden": true }) })] }));
58
61
  }
package/dist/dev.js CHANGED
@@ -44,7 +44,7 @@ import { showSettings, useOpen } from './open.js';
44
44
  const LINKS = {
45
45
  github: app('-/sync'),
46
46
  customize: app('-/customize'),
47
- settings: app('-/settings'),
47
+ settings: '/settings',
48
48
  home: '/',
49
49
  };
50
50
  /** The builder's address for the plans. */
@@ -80,15 +80,23 @@ export function useDevHost() {
80
80
  const here = path(route(at(pathname)));
81
81
  const look = useLook();
82
82
  const { settings } = useOpen();
83
- // The builder names every Settings section the rooms' pane does that it has;
84
- // one it does not have opens Settings itself.
83
+ // Settings is top-level /settings; organization settings are at /settings/organization.
85
84
  useEffect(() => {
86
85
  if (!settings)
87
86
  return;
88
87
  showSettings(null);
89
- const asked = route(`-/settings/${settings}`);
90
- go(app(path(asked.kind === 'settings' ? asked : { kind: 'settings', section: 'general' })));
91
- }, [settings]);
88
+ const s = settings.toLowerCase();
89
+ if (s.includes('member'))
90
+ router.push('/settings/organization/members');
91
+ else if (s.includes('billing'))
92
+ router.push('/settings/organization/billing');
93
+ else if (s.includes('usage'))
94
+ router.push('/settings/organization/usage');
95
+ else if (s.includes('org'))
96
+ router.push('/settings/organization');
97
+ else
98
+ router.push('/settings');
99
+ }, [settings, router]);
92
100
  // The token's claims change only when the token does: a sign-in, a refresh.
93
101
  const memberships = useMemo(() => orgs(), [accessToken]); // eslint-disable-line react-hooks/exhaustive-deps
94
102
  const scoped = useMemo(() => org(), [accessToken]); // eslint-disable-line react-hooks/exhaustive-deps
package/dist/index.js CHANGED
@@ -10,4 +10,3 @@
10
10
  */
11
11
  export { configure, where, site, brand } from './where.js';
12
12
  export { Rooms, Look, useRooms, useLook, useOrg } from './host.js';
13
- // The bar over a focused page (the chooser, the way in, the way to pay): one for every host.
@@ -10,5 +10,10 @@ export declare const OUT = "/login?from=logout";
10
10
  * IAM's /v1/iam routes, IAM's session here ended, the local session cleared —
11
11
  * counted as logout_completed, and then /login?from=logout, where the person can
12
12
  * sign straight back in. IAM's own logout page is never visited.
13
+ *
14
+ * EVERY HANZO SITE AT ONCE (`everywhere`). hanzo.ai and platform.hanzo.ai sign in
15
+ * from one session, so leaving one of them must leave the other: IAM retires the
16
+ * person's grant to every application of this client's organization along with
17
+ * the session. A third-party app the person signed into keeps its own.
13
18
  */
14
19
  export declare function useSignOut(): () => Promise<void>;
@@ -28,7 +28,13 @@ export const OUT = '/login?from=logout';
28
28
  * IAM's /v1/iam routes, IAM's session here ended, the local session cleared —
29
29
  * counted as logout_completed, and then /login?from=logout, where the person can
30
30
  * sign straight back in. IAM's own logout page is never visited.
31
+ *
32
+ * EVERY HANZO SITE AT ONCE (`everywhere`). hanzo.ai and platform.hanzo.ai sign in
33
+ * from one session, so leaving one of them must leave the other: IAM retires the
34
+ * person's grant to every application of this client's organization along with
35
+ * the session. A third-party app the person signed into keeps its own.
31
36
  */
32
37
  export function useSignOut() {
33
- return useUiSignOut({ to: OUT, track });
38
+ // @ts-expect-error everywhere option added in @hanzo/ui / IAM shared sign-out
39
+ return useUiSignOut({ to: OUT, track, everywhere: true });
34
40
  }
@@ -0,0 +1,24 @@
1
+ /** The pane left in sight at the end edge while the menu is open, px: a thumb's 44 and room around it. */
2
+ export declare const STRIP = 64;
3
+ /** The menu's widest, px: a phone held sideways, or a small tablet, keeps the menu a menu. */
4
+ export declare const REACH = 360;
5
+ /** How far the pane slides, as CSS: the window less the strip, up to `REACH`. `%` is the window's width wherever it is read. */
6
+ export declare const OPEN = "min(100% - 64px, 360px)";
7
+ /** Shut (no menu), open, or sliding home before the menu goes. */
8
+ export type State = 'shut' | 'open' | 'closing';
9
+ /** Which way the end side is, as a sign on the x axis. */
10
+ export declare const sign: (direction: string) => 1 | -1;
11
+ /** px a finger moves before a drag is a drag, and not a tap or a scroll. */
12
+ export declare const SLOP = 8;
13
+ /**
14
+ * What a finger's first movement is: a pull along the line (`x`), a scroll
15
+ * (`y`), or nothing yet. Along the line has to win clearly: a list scrolls on a
16
+ * slant.
17
+ */
18
+ export declare function axis(dx: number, dy: number): 'x' | 'y' | null;
19
+ /** Where the pane stands while a finger holds it, px from home: where it was, moved along the line, never past home or open. */
20
+ export declare function pull(from: number, dx: number, way: 1 | -1, reach: number): number;
21
+ /** A flick, px per ms along the line: faster than this decides by its way, whatever the distance. */
22
+ export declare const FLICK = 0.4;
23
+ /** Whether a pane let go at `at` px from home, moving `speed` px/ms along the line, stays open. */
24
+ export declare function settle(at: number, reach: number, speed: number): boolean;
package/dist/slide.js ADDED
@@ -0,0 +1,48 @@
1
+ // The phone's drawer as numbers (Drawer.tsx draws it): how far the pane slides,
2
+ // which way, and where a drag lets it go.
3
+ //
4
+ // The pane slides toward the END side of the line: right where text runs left to
5
+ // right, left under `dir="rtl"`. A transform has no logical form, so its sign is
6
+ // the one physical fact in the drawer, and it is read off the page
7
+ // (`getComputedStyle(…).direction`), never assumed. Everything else is logical:
8
+ // the menu stands at the start, the strip of pane left in sight at the end.
9
+ //
10
+ // A drag is measured ALONG the line: positive toward the end, whichever way the
11
+ // line runs, so the same numbers close an LTR drawer pulled left and an RTL one
12
+ // pulled right.
13
+ /** The pane left in sight at the end edge while the menu is open, px: a thumb's 44 and room around it. */
14
+ export const STRIP = 64;
15
+ /** The menu's widest, px: a phone held sideways, or a small tablet, keeps the menu a menu. */
16
+ export const REACH = 360;
17
+ /** How far the pane slides, as CSS: the window less the strip, up to `REACH`. `%` is the window's width wherever it is read. */
18
+ export const OPEN = `min(100% - ${STRIP}px, ${REACH}px)`;
19
+ /** Which way the end side is, as a sign on the x axis. */
20
+ export const sign = (direction) => (direction === 'rtl' ? -1 : 1);
21
+ /** px a finger moves before a drag is a drag, and not a tap or a scroll. */
22
+ export const SLOP = 8;
23
+ /**
24
+ * What a finger's first movement is: a pull along the line (`x`), a scroll
25
+ * (`y`), or nothing yet. Along the line has to win clearly: a list scrolls on a
26
+ * slant.
27
+ */
28
+ export function axis(dx, dy) {
29
+ const x = Math.abs(dx);
30
+ const y = Math.abs(dy);
31
+ if (x < SLOP && y < SLOP)
32
+ return null;
33
+ return x > y * 1.2 ? 'x' : 'y';
34
+ }
35
+ /** Where the pane stands while a finger holds it, px from home: where it was, moved along the line, never past home or open. */
36
+ export function pull(from, dx, way, reach) {
37
+ return Math.min(reach, Math.max(0, from + dx * way));
38
+ }
39
+ /** A flick, px per ms along the line: faster than this decides by its way, whatever the distance. */
40
+ export const FLICK = 0.4;
41
+ /** Whether a pane let go at `at` px from home, moving `speed` px/ms along the line, stays open. */
42
+ export function settle(at, reach, speed) {
43
+ if (speed <= -FLICK)
44
+ return false;
45
+ if (speed >= FLICK)
46
+ return true;
47
+ return at > reach / 2;
48
+ }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@hanzo/rooms",
3
- "version": "0.1.96",
3
+ "version": "0.1.98",
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",
@@ -53,6 +53,7 @@
53
53
  "@hanzo/composer": ">=0.2.0",
54
54
  "@hanzo/event": ">=0.3.56",
55
55
  "@hanzo/gui": "^8.3.5",
56
+ "@hanzo/i18n": ">=0.2.0",
56
57
  "@hanzo/iam": ">=0.21.35",
57
58
  "@hanzo/logo": ">=1.0.26",
58
59
  "@hanzo/studio": ">=0.1.0",
@@ -73,10 +74,11 @@
73
74
  "@hanzo/design": "0.5.34",
74
75
  "@hanzo/event": "0.3.56",
75
76
  "@hanzo/gui": "8.3.5",
77
+ "@hanzo/i18n": "0.2.0",
76
78
  "@hanzo/iam": "0.21.35",
77
79
  "@hanzo/logo": "1.0.26",
78
80
  "@hanzo/studio": "0.1.0",
79
- "@hanzo/tabs": "github:hanzo-apps/tabs#af6f1e4c02a3ef7faa192a8310e193219fa5c1c5",
81
+ "@hanzo/tabs": "github:hanzo-inc/tabs",
80
82
  "@hanzo/ui": "8.29.0",
81
83
  "@hanzo/usage": "0.1.18",
82
84
  "@hanzo/voice": "0.1.19",
@@ -88,6 +90,7 @@
88
90
  "@types/node": "26.5.0",
89
91
  "@types/react": "19.2.18",
90
92
  "@types/react-dom": "19.2.7",
93
+ "happy-dom": "20.14.6",
91
94
  "lucide-react": "1.42.0",
92
95
  "react": "19.2.8",
93
96
  "react-dom": "19.2.8",