@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/App.js CHANGED
@@ -1,5 +1,5 @@
1
1
  'use client';
2
- import { Fragment as _Fragment, jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
+ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
3
3
  // The web app as the whole window, in three modes: Chat, a conversation with
4
4
  // the product's AI; Dev, @hanzo/build's `<Builder>` without a rail of its own;
5
5
  // and Studio, @hanzo/studio's `<Studio>` the same way, its places (`nav`) in the
@@ -47,7 +47,9 @@ import { Fragment as _Fragment, jsx as _jsx, jsxs as _jsxs } from "react/jsx-run
47
47
  // nothing fills it. So Dev's pane is cut on its own and the tabs are Chat's.
48
48
  //
49
49
  // EVERY MOVE PUTS THE DRAWER AWAY, the one to where the reader already is
50
- // included.
50
+ // included. A phone's drawer (Drawer.tsx) is the menu behind the pane, the pane
51
+ // sliding toward the end side over it; a move made from it takes its place in
52
+ // the history (`depart`), so Back never reopens it.
51
53
  //
52
54
  // STUDIO LOADS WITH ITS MODE. Its module (and three.js behind a 3D tab) is
53
55
  // fetched the first time the reader stands in Studio; its places are a few
@@ -70,6 +72,7 @@ import { Orgs } from './Orgs.js';
70
72
  import { useRooms } from './host.js';
71
73
  import { useKept } from './kept.js';
72
74
  import { Mark, Rail, spot, Toggle } from './Rail.js';
75
+ import { Foot, Layer, Lift, RAISED, useDrawer } from './Drawer.js';
73
76
  import { empty, openThread, useOpen } from './open.js';
74
77
  import { Paned } from './Shell.js';
75
78
  import { Palette } from './Palette.js';
@@ -87,18 +90,6 @@ const Terminals = lazy(() => import('./Terminals.js'));
87
90
  const HOME = { mode: 'chat', thread: null };
88
91
  /** The bar's height, px: the reference window's title bar. */
89
92
  const BAR = 40;
90
- /**
91
- * The dim under the phone's drawer: design's scrim at two fifths, so the page
92
- * still reads through the drawer's glass. gui carries a `color-mix()` to the
93
- * page unchanged; its types only name custom properties.
94
- */
95
- const DIM = 'color-mix(in srgb, var(--surface-scrim) 40%, transparent)';
96
- /** The phone's drawer stands the gutter off each edge, or the device's inset where that is wider. */
97
- const INSET = {
98
- top: 'max(var(--pane-gap), var(--safe-top))',
99
- bottom: 'max(var(--pane-gap), var(--safe-bottom))',
100
- left: 'max(var(--pane-gap), var(--safe-left))',
101
- };
102
93
  /** Where each mode was last, so the rail goes back to it. */
103
94
  const last = { chat: '', dev: '', studio: '', tabs: '' };
104
95
  /** Whether a key press is the reader typing, where the app's keys are the field's. */
@@ -113,24 +104,26 @@ export function App({ bar } = {}) {
113
104
  }
114
105
  /** The window itself, drawn once the organization is settled. */
115
106
  function Window({ bar }) {
116
- const [drawer, setDrawer] = useState(false);
107
+ const drawer = useDrawer();
108
+ const { close: shut, depart } = drawer;
117
109
  // On a phone the window stands in the visual viewport, so the keyboard never covers the composer (view.ts).
118
110
  const view = useView();
119
- const shut = useCallback(() => setDrawer(false), []);
111
+ const hold = drawer.window;
112
+ const windowRef = useCallback((node) => {
113
+ view(node);
114
+ hold(node);
115
+ }, [view, hold]);
120
116
  // The sidebar shut, kept per browser under the key the builder's rail kept it under.
121
117
  const [closed, setClosed] = useKept('hanzo.build.rail', false);
122
118
  // The palette is open (the sidebar's search; ⌘K toggles it).
123
119
  const [finding, setFinding] = useState(false);
124
120
  const found = useCallback(() => setFinding(false), []);
125
121
  const dev = useDevHost();
126
- // The builder's moves put the drawer away first.
122
+ // The builder's moves put the drawer away, in its place in the history.
127
123
  const host = useMemo(() => ({
128
124
  ...dev,
129
- go: (to, how) => {
130
- shut();
131
- dev.go(to, how);
132
- },
133
- }), [dev, shut]);
125
+ go: (to, how) => depart((replace) => dev.go(to, replace ? { ...how, replace } : how)),
126
+ }), [dev, depart]);
134
127
  const { client } = useAi();
135
128
  const { router, path } = useRooms();
136
129
  const here = path ?? '/';
@@ -144,15 +137,12 @@ function Window({ bar }) {
144
137
  ...host,
145
138
  name: 'Hanzo Studio',
146
139
  path: made,
147
- go: (to, how) => {
148
- shut();
149
- go(studio(to), how?.replace);
150
- },
140
+ go: (to, how) => depart((replace) => go(studio(to), replace || how?.replace)),
151
141
  chooseOrg: (next) => {
152
142
  pick(next);
153
143
  window.location.assign(studio(''));
154
144
  },
155
- }), [host, made, shut]);
145
+ }), [host, made, depart]);
156
146
  // A run floats two panes of its own, so the pane it stands in is not cut.
157
147
  const run = where !== null && route(where).kind === 'run';
158
148
  const asked = at.mode === 'chat' ? at.thread : null;
@@ -161,8 +151,8 @@ function Window({ bar }) {
161
151
  // the address does not move (the reader was on that page already).
162
152
  useEffect(() => {
163
153
  if (settings)
164
- setDrawer(false);
165
- }, [settings]);
154
+ shut();
155
+ }, [settings, shut]);
166
156
  const plans = where !== null && isPlans(where);
167
157
  useEffect(() => {
168
158
  if (plans)
@@ -195,10 +185,12 @@ function Window({ bar }) {
195
185
  // The selection writes the address only when it moves.
196
186
  // eslint-disable-next-line react-hooks/exhaustive-deps
197
187
  }, [mode, thread]);
188
+ // The address moved under the drawer: it goes, and the history keeps the move.
189
+ const drop = drawer.drop;
198
190
  useEffect(() => {
199
191
  last[mode] = `${window.location.pathname}${window.location.search}`;
200
- setDrawer(false);
201
- }, [mode, here]);
192
+ drop();
193
+ }, [mode, here, drop]);
202
194
  // Where the reader was before a place that takes the sidebar (Library, Scheduled
203
195
  // tasks, Customize, Settings): its Back returns there, Chat or Dev alike.
204
196
  const away = useRef('');
@@ -207,11 +199,8 @@ function Window({ bar }) {
207
199
  if (!inPlace)
208
200
  away.current = `${window.location.pathname}${window.location.search}`;
209
201
  }, [inPlace, here]);
210
- /** A move of the app's own: the drawer away, then the address. */
211
- const move = useCallback((address) => {
212
- shut();
213
- go(address);
214
- }, [shut]);
202
+ /** A move of the app's own: the drawer away, the address in its place. */
203
+ const move = useCallback((address) => depart((replace) => go(address, replace)), [depart]);
215
204
  // The app's keys.
216
205
  useEffect(() => {
217
206
  const key = (e) => {
@@ -248,6 +237,11 @@ function Window({ bar }) {
248
237
  const toDev = () => move(last.dev || app(''));
249
238
  const toStudio = () => move(last.studio || studio(''));
250
239
  const toTabs = () => move(TERMINALS);
240
+ /** The drawer's New chat. */
241
+ const fresh = () => {
242
+ empty();
243
+ move(talk());
244
+ };
251
245
  // Another organization: picked, and the app opened in it at the mode's New.
252
246
  const org = (o) => {
253
247
  pick(o);
@@ -259,5 +253,7 @@ function Window({ bar }) {
259
253
  shut();
260
254
  setFinding(true);
261
255
  };
262
- return (_jsx(AiProvider, { client: client ?? undefined, children: _jsx(HostProvider, { host: host, children: _jsx(PrefsProvider, { children: _jsxs(YStack, { ref: view, "data-slot": "window", ...(bar ? floor : null), flex: 1, minW: 0, height: "100dvh", overflow: "hidden", "$max-md": { position: 'fixed', t: TOP, l: 0, r: 0, height: HEIGHT }, children: [bar ? (_jsxs(XStack, { "data-slot": "title-bar", height: BAR, shrink: 0, items: "center", gap: 2, pl: (bar.inset ?? 0) + 6, pr: "$2", ...bar.drag, children: [_jsx(XStack, { "$max-md": { display: 'none' }, children: _jsx(Toggle, { open: !closed, onPress: flip }) }), _jsx(XStack, { $md: { display: 'none' }, children: _jsx(Toggle, { open: drawer, onPress: () => setDrawer(true), label: "Open sidebar" }) }), _jsx(Mark, { label: "Back", "data-slot": "back", onPress: () => router.back(), children: _jsx(ArrowLeft, { size: 18, "aria-hidden": true }) }), _jsx(Mark, { label: "Forward", "data-slot": "forward", onPress: () => router.forward(), children: _jsx(ArrowRight, { size: 18, "aria-hidden": true }) }), _jsx(XStack, { flex: 1, self: "stretch", ...bar.drag })] })) : null, _jsxs(XStack, { "data-room": "", "data-app": mode, "data-slot": "frame", ...(bar ? null : floor), flex: 1, minW: 0, minH: 0, pr: "var(--pane-gap)", pb: "var(--pane-gap)", pt: bar ? 0 : 'var(--pane-gap)', "$max-md": { p: 0 }, children: [_jsx(YStack, { height: "100%", "$max-md": { display: 'none' }, children: _jsx(Rail, { host: host, mode: mode, at: where, open: !closed, toggle: bar ? undefined : flip, onChat: toChat, onDev: toDev, onStudio: toStudio, onTabs: toTabs, go: move, onOrg: org }) }), closed || mode === 'tabs' ? null : (_jsx(YStack, { "$max-md": { display: 'none' }, children: _jsx(Column, { host: host, mode: mode, thread: thread, go: move, onShut: () => setClosed(true), onSeek: seek, onBack: back, places: sidebar }) })), _jsxs(YStack, { "data-slot": "stage", flex: 1, minW: 0, minH: 0, ml: "var(--pane-gap)", "$max-md": { ml: 0, pb: FOOT }, children: [bar ? null : _jsx(Top, { host: host, mode: mode, places: sidebar, go: move, onMenu: () => setDrawer(true), onChat: toChat, onDev: toDev }), _jsx(XStack, { "data-slot": "panes", flex: 1, minW: 0, minH: 0, children: mode === 'chat' ? (_jsx(Paned, { framed: true, children: _jsx(Chat, {}) })) : mode === 'tabs' ? (_jsx(YStack, { render: "main", "data-slot": "pane", ...pane(), overflow: "hidden", flex: 1, minW: 0, minH: 0, children: _jsx(Suspense, { fallback: null, children: _jsx(Terminals, {}) }) })) : mode === 'studio' ? (_jsx(YStack, { render: "main", "data-slot": "pane", ...pane(), overflow: "hidden", flex: 1, minW: 0, minH: 0, children: _jsx(YStack, { flex: 1, minH: 0, height: "100%", children: _jsx(Suspense, { fallback: null, children: _jsx(Studio, { host: maker, rail: false }) }) }) })) : (_jsx(YStack, { render: "main", "data-slot": "pane", ...(run ? null : { ...pane(), overflow: 'hidden' }), flex: 1, minW: 0, minH: 0, children: plans ? null : _jsx(Builder, { host: host, rail: false }) })) })] })] }), drawer ? (_jsxs(_Fragment, { children: [_jsx(YStack, { render: "button", "aria-label": "Close sidebar", onPress: () => setDrawer(false), bg: DIM, position: "absolute", t: 0, r: 0, b: 0, l: 0, z: "var(--z-overlay)", $md: { display: 'none' } }), _jsx(YStack, { "data-slot": "drawer", position: "absolute", t: INSET.top, b: INSET.bottom, l: INSET.left, width: "min(320px, calc(100% - 2 * var(--pane-gap) - 48px))", z: "var(--z-modal)", $md: { display: 'none' }, children: _jsx(Column, { host: host, mode: mode, thread: thread, go: move, onShut: shut, onSeek: seek, onBack: back, drawer: true, places: sidebar, foot: _jsx(Rail, { host: host, mode: mode, at: where, open: true, flat: true, onChat: toChat, onDev: toDev, onStudio: toStudio, onTabs: toTabs, go: move, onOrg: org }) }) })] })) : null, finding ? _jsx(Palette, { go: move, onClose: found, mode: mode, host: host }) : null] }) }) }) }));
256
+ return (_jsx(AiProvider, { client: client ?? undefined, children: _jsx(HostProvider, { host: host, children: _jsx(PrefsProvider, { children: _jsxs(YStack, { ref: windowRef, "data-slot": "window", ...(bar ? floor : null), flex: 1, minW: 0, height: "100dvh", overflow: "hidden", "$max-md": { position: 'fixed', t: TOP, l: 0, r: 0, height: HEIGHT, ...{ overflow: 'clip' } }, children: [bar ? (_jsxs(XStack, { "data-slot": "title-bar", height: BAR, shrink: 0, items: "center", gap: 2, pl: (bar.inset ?? 0) + 6, pr: "$2", ...bar.drag, children: [_jsx(XStack, { "$max-md": { display: 'none' }, children: _jsx(Toggle, { open: !closed, onPress: flip }) }), _jsx(XStack, { $md: { display: 'none' }, children: _jsx(Toggle, { open: drawer.state === 'open', onPress: drawer.open, label: "Open sidebar" }) }), _jsx(Mark, { label: "Back", "data-slot": "back", onPress: () => router.back(), children: _jsx(ArrowLeft, { size: 18, "aria-hidden": true }) }), _jsx(Mark, { label: "Forward", "data-slot": "forward", onPress: () => router.forward(), children: _jsx(ArrowRight, { size: 18, "aria-hidden": true }) }), _jsx(XStack, { flex: 1, self: "stretch", ...bar.drag })] })) : null, _jsxs(XStack, { ref: drawer.pane, "data-room": "", "data-app": mode, "data-slot": "frame", ...(bar ? null : floor), ...(drawer.slide ? RAISED : null),
257
+ // inline-style: the slide, a transform with no logical form whose sign and distance the drawer writes on the element itself, a drag every frame (Drawer.tsx).
258
+ style: drawer.slide ?? undefined, flex: 1, minW: 0, minH: 0, pr: "var(--pane-gap)", pb: "var(--pane-gap)", pt: bar ? 0 : 'var(--pane-gap)', "$max-md": { p: 0 }, children: [drawer.slide ? _jsx(Lift, {}) : null, _jsx(YStack, { height: "100%", "$max-md": { display: 'none' }, children: _jsx(Rail, { host: host, mode: mode, at: where, open: !closed, toggle: bar ? undefined : flip, onChat: toChat, onDev: toDev, onStudio: toStudio, onTabs: toTabs, go: move, onOrg: org }) }), closed || mode === 'tabs' ? null : (_jsx(YStack, { "$max-md": { display: 'none' }, children: _jsx(Column, { host: host, mode: mode, thread: thread, go: move, onShut: () => setClosed(true), onSeek: seek, onBack: back, places: sidebar }) })), _jsxs(YStack, { "data-slot": "stage", flex: 1, minW: 0, minH: 0, ml: "var(--pane-gap)", "$max-md": { ml: 0, pb: FOOT }, children: [bar ? null : _jsx(Top, { host: host, mode: mode, places: sidebar, go: move, onMenu: drawer.open, onChat: toChat, onDev: toDev }), _jsx(XStack, { "data-slot": "panes", flex: 1, minW: 0, minH: 0, children: mode === 'chat' ? (_jsx(Paned, { framed: true, children: _jsx(Chat, {}) })) : mode === 'tabs' ? (_jsx(YStack, { render: "main", "data-slot": "pane", ...pane(), overflow: "hidden", flex: 1, minW: 0, minH: 0, children: _jsx(Suspense, { fallback: null, children: _jsx(Terminals, {}) }) })) : mode === 'studio' ? (_jsx(YStack, { render: "main", "data-slot": "pane", ...pane(), overflow: "hidden", flex: 1, minW: 0, minH: 0, children: _jsx(YStack, { flex: 1, minH: 0, height: "100%", children: _jsx(Suspense, { fallback: null, children: _jsx(Studio, { host: maker, rail: false }) }) }) })) : (_jsx(YStack, { render: "main", "data-slot": "pane", ...(run ? null : { ...pane(), overflow: 'hidden' }), flex: 1, minW: 0, minH: 0, children: plans ? null : _jsx(Builder, { host: host, rail: false }) })) })] })] }), drawer.state === 'shut' ? null : (_jsx(Layer, { drawer: drawer, top: bar ? BAR : 0, children: _jsx(Column, { host: host, mode: mode, thread: thread, go: move, onShut: shut, onSeek: seek, onBack: back, drawer: true, places: sidebar, rail: _jsx(Rail, { host: host, mode: mode, at: where, open: true, rows: true, onChat: toChat, onDev: toDev, onStudio: toStudio, onTabs: toTabs, go: move, onOrg: org }), foot: _jsx(Foot, { host: host, onChat: fresh, onOrg: org }) }) })), finding ? _jsx(Palette, { go: move, onClose: found, mode: mode, host: host }) : null] }) }) }) }));
263
259
  }
package/dist/Chrome.js CHANGED
@@ -1,5 +1,6 @@
1
1
  'use client';
2
2
  import { Fragment as _Fragment, jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
3
+ import { useI18n } from '@hanzo/i18n/react';
3
4
  import { useIam } from '@hanzo/iam/react';
4
5
  import { Text, View, XStack, YStack } from '@hanzo/gui';
5
6
  import { Avatar, Button, DropdownMenu, DropdownMenuContent, DropdownMenuItem, DropdownMenuLabel, DropdownMenuSeparator, DropdownMenuTrigger } from '@hanzo/ui';
@@ -13,6 +14,7 @@ const HEIGHT = 60;
13
14
  /** The notch's inset. gui carries a CSS function to the page unchanged; its types only name custom properties. */
14
15
  const NOTCH = 'env(safe-area-inset-top)';
15
16
  export function Chrome({ back }) {
17
+ const { t } = useI18n();
16
18
  const { user, isAuthenticated } = useIam();
17
19
  const signOut = useSignOut();
18
20
  const claimed = named();
@@ -27,5 +29,5 @@ export function Chrome({ back }) {
27
29
  };
28
30
  const out = () => void signOut();
29
31
  const signed = isAuthenticated && Boolean(who);
30
- return (_jsxs(XStack, { render: "header", "data-slot": "masthead", position: "sticky", t: 0, z: "var(--z-header)", shrink: 0, boxSizing: "content-box", height: HEIGHT, pt: NOTCH, px: "var(--page-gutter, 20px)", gap: 12, items: "center", bg: "var(--background)", borderBottomWidth: 1, borderColor: "var(--border)", children: [_jsx(Text, { render: _jsx("a", { href: "/", "aria-label": `${brand()} home` }), display: "flex", items: "center", shrink: 0, color: "var(--foreground)", textDecorationLine: "none", children: _jsx(HanzoLockup, { label: brand(), size: 22 }) }), back, _jsx(View, { flex: 1 }), signed ? (_jsxs(_Fragment, { children: [_jsxs(XStack, { render: "section", "aria-label": "Account", items: "center", gap: 12, minW: 0, "$max-md": { display: 'none' }, children: [_jsx(Avatar, { src: avatar || null, name: who, alt: "" }), _jsxs(YStack, { minW: 0, gap: 2, children: [_jsx(Text, { fontSize: "$3", fontWeight: "500", color: "$ink", numberOfLines: 1, children: who }), name && email ? (_jsx(Text, { fontSize: "$2", color: "$soft", numberOfLines: 1, children: email })) : null] }), _jsx(Button, { variant: "ghost", size: "sm", onClick: other, children: "Use another account" }), _jsx(Button, { variant: "ghost", size: "sm", onClick: out, children: "Log out" })] }), _jsx(View, { display: "none", "$max-md": { display: 'flex' }, children: _jsxs(DropdownMenu, { placement: "bottom-end", offset: 8, children: [_jsx(DropdownMenuTrigger, { asChild: true, children: _jsx(XStack, { render: "button", "aria-label": `Account: ${who}`, width: 40, height: 40, rounded: 999, items: "center", justify: "center", borderWidth: 0, bg: "transparent", hoverStyle: { bg: '$hover' }, focusVisibleStyle: { outlineWidth: 2, outlineStyle: 'solid', outlineColor: '$outlineColor' }, cursor: "pointer", children: _jsx(Avatar, { src: avatar || null, name: who, alt: "" }) }) }), _jsxs(DropdownMenuContent, { minW: 240, maxW: "calc(100vw - 16px)", "aria-labelledby": undefined, "aria-label": "Account", children: [_jsx(DropdownMenuLabel, { children: _jsxs(YStack, { minW: 0, gap: 2, children: [_jsx(Text, { fontSize: "$3", fontWeight: "500", color: "$ink", numberOfLines: 1, children: who }), name && email ? (_jsx(Text, { fontSize: "$2", color: "$soft", numberOfLines: 1, children: email })) : null] }) }), _jsx(DropdownMenuSeparator, {}), _jsx(DropdownMenuItem, { onSelect: other, children: _jsx(Text, { fontSize: "$3", color: "$ink", children: "Use another account" }) }), _jsx(DropdownMenuItem, { onSelect: out, children: _jsx(Text, { fontSize: "$3", color: "$ink", children: "Log out" }) })] })] }) })] })) : null] }));
32
+ return (_jsxs(XStack, { render: "header", "data-slot": "masthead", position: "sticky", t: 0, z: "var(--z-header)", shrink: 0, boxSizing: "content-box", height: HEIGHT, pt: NOTCH, px: "var(--page-gutter, 20px)", gap: 12, items: "center", bg: "var(--background)", borderBottomWidth: 1, borderColor: "var(--border)", children: [_jsx(Text, { render: _jsx("a", { href: "/", "aria-label": t('{brandName} home', { brandName: brand() }) }), display: "flex", items: "center", shrink: 0, color: "var(--foreground)", textDecorationLine: "none", children: _jsx(HanzoLockup, { label: brand(), size: 22 }) }), back, _jsx(View, { flex: 1 }), signed ? (_jsxs(_Fragment, { children: [_jsxs(XStack, { render: "section", "aria-label": t('Account'), items: "center", gap: 12, minW: 0, "$max-md": { display: 'none' }, children: [_jsx(Avatar, { src: avatar || null, name: who, alt: "" }), _jsxs(YStack, { minW: 0, gap: 2, children: [_jsx(Text, { fontSize: "$3", fontWeight: "500", color: "$ink", numberOfLines: 1, children: who }), name && email ? (_jsx(Text, { fontSize: "$2", color: "$soft", numberOfLines: 1, children: email })) : null] }), _jsx(Button, { variant: "ghost", size: "sm", onClick: other, children: t('Use another account') }), _jsx(Button, { variant: "ghost", size: "sm", onClick: out, children: t('Log out') })] }), _jsx(View, { display: "none", "$max-md": { display: 'flex' }, children: _jsxs(DropdownMenu, { placement: "bottom-end", offset: 8, children: [_jsx(DropdownMenuTrigger, { asChild: true, children: _jsx(XStack, { render: "button", "aria-label": t('Account: {name}', { name: who }), width: 40, height: 40, rounded: 999, items: "center", justify: "center", borderWidth: 0, bg: "transparent", hoverStyle: { bg: '$hover' }, focusVisibleStyle: { outlineWidth: 2, outlineStyle: 'solid', outlineColor: '$outlineColor' }, cursor: "pointer", children: _jsx(Avatar, { src: avatar || null, name: who, alt: "" }) }) }), _jsxs(DropdownMenuContent, { minW: 240, maxW: "calc(100vw - 16px)", "aria-labelledby": undefined, "aria-label": t('Account'), children: [_jsx(DropdownMenuLabel, { children: _jsxs(YStack, { minW: 0, gap: 2, children: [_jsx(Text, { fontSize: "$3", fontWeight: "500", color: "$ink", numberOfLines: 1, children: who }), name && email ? (_jsx(Text, { fontSize: "$2", color: "$soft", numberOfLines: 1, children: email })) : null] }) }), _jsx(DropdownMenuSeparator, {}), _jsx(DropdownMenuItem, { onSelect: other, children: _jsx(Text, { fontSize: "$3", color: "$ink", children: t('Use another account') }) }), _jsx(DropdownMenuItem, { onSelect: out, children: _jsx(Text, { fontSize: "$3", color: "$ink", children: t('Log out') }) })] })] }) })] })) : null] }));
31
33
  }
package/dist/Column.d.ts CHANGED
@@ -2,7 +2,7 @@ import { type Host } from '@hanzo/build';
2
2
  import { type RailLink } from '@hanzo/ui/chat';
3
3
  import { type ReactNode } from 'react';
4
4
  import { type Place } from './lib/host.js';
5
- export declare function Column({ host, mode, thread, go, onShut, onSeek, onBack, drawer, foot, places, }: {
5
+ export declare function Column({ host, mode, thread, go, onShut, onSeek, onBack, drawer, rail, foot, places, }: {
6
6
  host: Host;
7
7
  mode: Place['mode'];
8
8
  /** The Chat conversation the pane holds, or null. */
@@ -15,9 +15,11 @@ export declare function Column({ host, mode, thread, go, onShut, onSeek, onBack,
15
15
  onSeek?: () => void;
16
16
  /** Leaves a place that took the sidebar, for where the reader was before it. */
17
17
  onBack: () => void;
18
- /** Drawn in the phone's drawer: no edge, cut as a floating pane. */
18
+ /** Drawn in the phone's drawer (Drawer.tsx): on the floor behind the pane, no edge, no handle. */
19
19
  drawer?: boolean;
20
- /** What stands at its foot: the drawer's rail, laid flat. */
20
+ /** What stands under the mode's own rows: the drawer's places, the rail's as rows. */
21
+ rail?: ReactNode;
22
+ /** What stands at its foot: the drawer's account and New chat. */
21
23
  foot?: ReactNode;
22
24
  /** The mode's places, where they are not Dev's (Studio's `nav`). */
23
25
  places?: {
package/dist/Column.js CHANGED
@@ -3,7 +3,8 @@ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
3
3
  // The app's sidebar, beside the rail on the window's floor (App.tsx): the
4
4
  // mode's name with the bell and the search at its right, New, Dev's places in
5
5
  // Dev, the reader's Recents (Pinned first), and the first-run cards. Which
6
- // product it lists is the rail's to say; the account is at the rail's foot.
6
+ // product it lists is the rail's to say; the account is at the rail's foot, or
7
+ // on a phone at the foot of its menu (Drawer.tsx), where this column is the menu.
7
8
  //
8
9
  // It is the window, not a panel: no fill, no edge, the floor's own ground. The
9
10
  // content beside it is the panel, inset by the gutter.
@@ -20,6 +21,7 @@ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
20
21
  // Its width is the edge's in the gutter to its right: dragged, keyed or
21
22
  // double-clicked back (@hanzo/build's `Grip`), kept per browser. Pulled in past
22
23
  // its floor it shuts (`onShut`), as the sidebar's toggle shuts it.
24
+ import { useI18n } from '@hanzo/i18n/react';
23
25
  import { Text, XStack, YStack } from '@hanzo/gui';
24
26
  import { ArrowLeft, Bell, Search, Settings2, SquarePen } from 'lucide-react';
25
27
  import { DOTS, Gutter, library, NEW, own, path as written, sections, tabs, useScheduled, useSessions } from '@hanzo/build';
@@ -34,22 +36,11 @@ import { Setup } from './Setup.js';
34
36
  import { ago } from './recent.js';
35
37
  import { empty } from './open.js';
36
38
  import { useSpan } from './span.js';
37
- import { pane } from './ground.js';
38
39
  import { talk } from './lib/host.js';
39
40
  import { brand, wordmark } from './where.js';
40
41
  /** The sidebar fills the column. Its `width` is typed in px alone, though its frame takes any gui size — the type is
41
42
  * @hanzo/ui's to widen. */
42
43
  const FILL = { width: '100%' };
43
- /**
44
- * The phone's drawer floats over the page under the browser's own glass bar, so
45
- * it is cut like that bar: the composer's corner, the strong hairline, the lit
46
- * top edge and the floating drop. Its fill and blur stay the pane's.
47
- */
48
- const FLOAT = {
49
- borderRadius: 'var(--radius-composer)',
50
- borderColor: 'var(--border-strong)',
51
- boxShadow: 'var(--edge-highlight), var(--shadow-floating)',
52
- };
53
44
  /** The sidebar's width, px: its default, and the two a drag holds it between. */
54
45
  const WIDTH = 240;
55
46
  const FLOOR = 200;
@@ -65,15 +56,16 @@ const keys = (press) => ({
65
56
  press();
66
57
  },
67
58
  });
68
- /** A row of the sidebar: a pill on a 31px pitch, lit where the reader is. */
69
- const ROW = { rounded: '$4', minH: 31, py: 0, px: '$2' };
59
+ /** A row of the sidebar: a pill on a 31px pitch, a thumb's 44 on a touch screen, lit where the reader is. */
60
+ const ROW = { rounded: '$4', minH: 31, py: 0, px: '$2', $touchable: { minH: 44 } };
70
61
  /** The run activity behind the bell: the runs that moved last, newest first. */
71
62
  const RECENT = 6;
72
63
  /** What a place that takes the sidebar is called at its head. */
73
64
  const TITLE = { library: 'Library', scheduled: 'Scheduled tasks', customize: 'Customize', settings: 'Settings' };
74
- /** A header mark: a 16px glyph in a 32px target. */
75
- const HEAD = { width: 32, height: 32, rounded: '$3' };
65
+ /** A header mark: a 16px glyph in a 32px target, a thumb's 44 on a touch screen. */
66
+ const HEAD = { width: 32, height: 32, rounded: '$3', $touchable: { width: 44, height: 44 } };
76
67
  function Activity({ host }) {
68
+ const { t } = useI18n();
77
69
  const store = useSessions(host);
78
70
  const latest = [...store.value].sort((a, b) => (a.updatedAt < b.updatedAt ? 1 : -1)).slice(0, RECENT);
79
71
  const items = [
@@ -90,9 +82,10 @@ function Activity({ host }) {
90
82
  { type: 'separator', key: 'line' },
91
83
  { key: 'settings', label: 'Notification settings', icon: _jsx(Settings2, { size: 16, "aria-hidden": true }), onSelect: () => host.go(written({ kind: 'settings', section: 'notifications' })) },
92
84
  ];
93
- return (_jsx(DropdownMenu, { minWidth: 280, trigger: _jsx(XStack, { render: "button", "aria-label": "Activity", ...{ title: 'Activity' }, ...HEAD, items: "center", justify: "center", cursor: "pointer", opacity: 0.7, hoverStyle: { bg: '$hover', opacity: 1 }, focusVisibleStyle: { outlineWidth: 2, outlineStyle: 'solid', outlineColor: '$outlineColor' }, children: _jsx(Bell, { size: 16, "aria-hidden": true }) }), items: items }));
85
+ return (_jsx(DropdownMenu, { minWidth: 280, trigger: _jsx(XStack, { render: "button", "aria-label": t("Activity"), ...{ title: 'Activity' }, ...HEAD, items: "center", justify: "center", cursor: "pointer", opacity: 0.7, hoverStyle: { bg: '$hover', opacity: 1 }, focusVisibleStyle: { outlineWidth: 2, outlineStyle: 'solid', outlineColor: '$outlineColor' }, children: _jsx(Bell, { size: 16, "aria-hidden": true }) }), items: items }));
94
86
  }
95
- export function Column({ host, mode, thread, go, onShut, onSeek, onBack, drawer = false, foot, places, }) {
87
+ export function Column({ host, mode, thread, go, onShut, onSeek, onBack, drawer = false, rail, foot, places, }) {
88
+ const { t } = useI18n();
96
89
  // The desktop sidebar and a phone's drawer can both be in the page: each names its own group labels.
97
90
  const uid = useId();
98
91
  const span = useSpan(SPAN, WIDTH, FLOOR, CEIL);
@@ -118,5 +111,5 @@ export function Column({ host, mode, thread, go, onShut, onSeek, onBack, drawer
118
111
  };
119
112
  return (_jsxs(YStack, { "data-slot": "app-column", ref: drawer ? undefined : span.ref,
120
113
  // inline-style: `--span`, the width a drag rewrites on the element itself (span.ts); no prop sets a variable.
121
- style: drawer ? undefined : span.style, position: "relative", flex: drawer ? 1 : undefined, shrink: drawer ? 1 : 0, minW: 0, height: "100%", width: drawer ? undefined : 'var(--span)', children: [_jsxs(Sidebar, { "data-slot": "app-rail", role: "navigation", "aria-label": brand(), ...(drawer ? { ...pane(), ...FLOAT } : { bg: 'transparent', borderWidth: 0 }), ...FILL, px: "$2", pt: drawer ? '$2' : 0, pb: "$2", gap: "$0", children: [at ? (_jsxs(XStack, { "data-slot": "app-head", items: "center", gap: "$1", pl: 2, pr: 2, height: 52, shrink: 0, minW: 0, children: [_jsx(SidebarIconButton, { label: "Back", "data-slot": "place-back", onPress: onBack, ...HEAD, children: _jsx(ArrowLeft, { size: 16, "aria-hidden": true }) }), _jsx(Text, { flex: 1, minW: 0, fontSize: 18, lineHeight: 24, fontWeight: "600", color: "$ink", numberOfLines: 1, "data-slot": "place-name", children: TITLE[at] })] })) : (_jsxs(XStack, { "data-slot": "app-head", items: "center", gap: "$1", pl: "$2", pr: 2, height: 52, shrink: 0, minW: 0, children: [_jsx(Text, { flex: 1, minW: 0, fontSize: 18, lineHeight: 24, fontWeight: "600", color: "$ink", numberOfLines: 1, children: wordmark(mode) }), _jsx(Activity, { host: host }), _jsx(SidebarIconButton, { label: "Search", onPress: onSeek, "aria-haspopup": "dialog", ...HEAD, children: _jsx(Search, { size: 16, "aria-hidden": true }) })] })), mode === 'chat' ? (_jsx(YStack, { role: "list", gap: 2, shrink: 0, children: _jsx(YStack, { role: "listitem", children: _jsx(SidebarItem, { "data-slot": "app-new", ...ROW, active: !thread, bg: "transparent", hoverStyle: { bg: '$hover' }, icon: _jsx(SquarePen, { size: 16, "aria-hidden": true }), onPress: create, ...keys(create), children: _jsx(Text, { fontSize: "$3", color: "$ink", children: "New chat" }) }) }) })) : (_jsx(YStack, { "data-slot": "app-places", shrink: 0, children: groups.map((g, i) => (_jsxs(YStack, { pt: i ? '$2' : 0, children: [i && g.label ? (_jsx(Text, { id: `${uid}-${g.id}`, pl: "$2", pb: 2, fontSize: "$3", color: "$soft", children: g.label })) : null, _jsx(YStack, { role: "list", "aria-labelledby": i && g.label ? `${uid}-${g.id}` : undefined, "aria-label": i && g.label ? undefined : g.label || (at ? TITLE[at] : g.id), gap: 1, children: g.links.map((link) => (_jsx(YStack, { role: "listitem", children: _jsx(SidebarItem, { ...ROW, ...(link.id === NEW ? { 'data-slot': 'app-new' } : {}), active: link.active, icon: link.id === NEW ? _jsx(SquarePen, { size: 16, "aria-hidden": true }) : link.icon, onPress: link.onPress, ...keys(() => link.onPress?.()), children: _jsx(Text, { fontSize: "$3", color: link.active ? '$ink' : '$quiet', children: link.label }) }) }, link.id))) })] }, g.id))) })), mode === 'studio' || mode === 'tabs' || at ? null : _jsx(Recents, { host: host, mode: mode, thread: thread, onOpen: open, onNew: create }), at ? null : (_jsxs(YStack, { gap: "$2", pt: "$2", shrink: 0, children: [_jsx(Made, {}), _jsx(Setup, {})] })), foot] }), drawer ? null : (_jsx(Gutter, { side: "left", span: span.span, floor: FLOOR, ceil: CEIL, reset: WIDTH, onSpan: span.move, onKeep: span.keep, onShut: onShut, label: "Resize sidebar" }))] }));
114
+ style: drawer ? undefined : span.style, position: "relative", flex: drawer ? 1 : undefined, shrink: drawer ? 1 : 0, minW: 0, height: "100%", width: drawer ? undefined : 'var(--span)', children: [_jsxs(Sidebar, { "data-slot": "app-rail", role: "navigation", "aria-label": brand(), bg: "transparent", borderWidth: 0, ...FILL, px: "$2", pt: drawer ? '$2' : 0, pb: "$2", gap: "$0", children: [at ? (_jsxs(XStack, { "data-slot": "app-head", items: "center", gap: "$1", pl: 2, pr: 2, height: 52, shrink: 0, minW: 0, children: [_jsx(SidebarIconButton, { label: t("Back"), "data-slot": "place-back", onPress: onBack, ...HEAD, children: _jsx(ArrowLeft, { size: 16, "aria-hidden": true }) }), _jsx(Text, { flex: 1, minW: 0, fontSize: 18, lineHeight: 24, fontWeight: "600", color: "$ink", numberOfLines: 1, "data-slot": "place-name", children: TITLE[at] })] })) : (_jsxs(XStack, { "data-slot": "app-head", items: "center", gap: "$1", pl: "$2", pr: 2, height: 52, shrink: 0, minW: 0, children: [_jsx(Text, { flex: 1, minW: 0, fontSize: 18, lineHeight: 24, fontWeight: "600", color: "$ink", numberOfLines: 1, children: wordmark(mode) }), _jsx(Activity, { host: host }), _jsx(SidebarIconButton, { label: t("Search"), onPress: onSeek, "aria-haspopup": "dialog", ...HEAD, children: _jsx(Search, { size: 16, "aria-hidden": true }) })] })), mode === 'chat' ? (_jsx(YStack, { role: "list", gap: 2, shrink: 0, children: _jsx(YStack, { role: "listitem", children: _jsx(SidebarItem, { "data-slot": "app-new", ...ROW, active: !thread, bg: "transparent", hoverStyle: { bg: '$hover' }, icon: _jsx(SquarePen, { size: 16, "aria-hidden": true }), onPress: create, ...keys(create), children: _jsx(Text, { fontSize: "$3", color: "$ink", children: t("New chat") }) }) }) })) : (_jsx(YStack, { "data-slot": "app-places", shrink: 0, children: groups.map((g, i) => (_jsxs(YStack, { pt: i ? '$2' : 0, children: [i && g.label ? (_jsx(Text, { id: `${uid}-${g.id}`, pl: "$2", pb: 2, fontSize: "$3", color: "$soft", children: g.label })) : null, _jsx(YStack, { role: "list", "aria-labelledby": i && g.label ? `${uid}-${g.id}` : undefined, "aria-label": i && g.label ? undefined : g.label || (at ? TITLE[at] : g.id), gap: 1, children: g.links.map((link) => (_jsx(YStack, { role: "listitem", children: _jsx(SidebarItem, { ...ROW, ...(link.id === NEW ? { 'data-slot': 'app-new' } : {}), active: link.active, icon: link.id === NEW ? _jsx(SquarePen, { size: 16, "aria-hidden": true }) : link.icon, onPress: link.onPress, ...keys(() => link.onPress?.()), children: _jsx(Text, { fontSize: "$3", color: link.active ? '$ink' : '$quiet', children: link.label }) }) }, link.id))) })] }, g.id))) })), rail, mode === 'studio' || mode === 'tabs' || at ? null : _jsx(Recents, { host: host, mode: mode, thread: thread, onOpen: open, onNew: create }), at ? null : (_jsxs(YStack, { gap: "$2", pt: "$2", shrink: 0, children: [_jsx(Made, {}), _jsx(Setup, {})] })), foot] }), drawer ? null : (_jsx(Gutter, { side: "left", span: span.span, floor: FLOOR, ceil: CEIL, reset: WIDTH, onSpan: span.move, onKeep: span.keep, onShut: onShut, label: t("Resize sidebar") }))] }));
122
115
  }
@@ -0,0 +1,55 @@
1
+ import { type CSSProperties, type ReactNode } from 'react';
2
+ import { type Who } from './Me.js';
3
+ import { type State } from './slide.js';
4
+ /** A press that moves the app: told whether it should take the menu's place in history. */
5
+ export type Move = (replace: boolean) => void;
6
+ export interface Drawer {
7
+ state: State;
8
+ open: () => void;
9
+ close: () => void;
10
+ /** Moves the app from the menu: the move replaces the menu's history entry, and the menu goes. */
11
+ depart: (move: Move) => void;
12
+ /** The address moved under the menu, by a way that was not `depart`: the menu goes, and its entry stays behind the move. */
13
+ drop: () => void;
14
+ /** The window: everything in it but the menu is inert while the menu is out. */
15
+ window: (node: unknown) => void;
16
+ /** The pane that slides. */
17
+ pane: (node: unknown) => void;
18
+ /** The menu's layer. */
19
+ layer: (node: unknown) => void;
20
+ /** The pane's slide, for its `style`: null while shut, so nothing is transformed and nothing is raised. */
21
+ slide: CSSProperties | null;
22
+ }
23
+ export declare function useDrawer(): Drawer;
24
+ /**
25
+ * The pane while the menu is out: raised over the floor, its edge drawn and a
26
+ * drop falling on the menu, clipped to its corners. Spread on the pane beside
27
+ * `slide`; `Lift` is its fill.
28
+ */
29
+ export declare const RAISED: {
30
+ readonly z: 'var(--z-raised)';
31
+ readonly overflow: 'hidden';
32
+ readonly boxShadow: '0 0 0 1px var(--pane-edge), var(--shadow-floating)';
33
+ };
34
+ /**
35
+ * The raised pane's fill: the pane's own material composited opaque, a step
36
+ * brighter than the floor the menu stands on, in both themes. Drawn UNDER the
37
+ * pane's content (`--z-under` inside the pane's own stacking) rather than as the
38
+ * pane's background, which is the frame's and a host may paint it at rest.
39
+ */
40
+ export declare function Lift(): import("react").JSX.Element;
41
+ /**
42
+ * The menu's layer: the menu at the start on the floor, and over the strip of
43
+ * pane left in sight the press that closes it. Drawn while the drawer is not shut.
44
+ */
45
+ export declare function Layer({ drawer, top, children }: {
46
+ drawer: Drawer;
47
+ top?: number;
48
+ children: ReactNode;
49
+ }): import("react").JSX.Element;
50
+ /** The menu's foot: the account (its menu opens above it) and the accent New chat. */
51
+ export declare function Foot({ host, onChat, onOrg }: {
52
+ host: Who;
53
+ onChat: () => void;
54
+ onOrg: (org: string) => void;
55
+ }): import("react").JSX.Element;