@hanzo/rooms 0.1.57 → 0.1.58
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.d.ts +13 -1
- package/dist/App.js +117 -36
- package/dist/Chat.js +20 -11
- package/dist/Column.d.ts +10 -6
- package/dist/Column.js +77 -54
- package/dist/Home.js +7 -7
- package/dist/Inbox.js +1 -1
- package/dist/Me.d.ts +7 -1
- package/dist/Me.js +35 -8
- package/dist/Meet.js +1 -1
- package/dist/Prose.js +16 -9
- package/dist/Rail.d.ts +39 -0
- package/dist/Rail.js +85 -0
- package/dist/Recents.js +21 -12
- package/dist/RightPane.d.ts +15 -0
- package/dist/RightPane.js +38 -9
- package/dist/Share.js +2 -2
- package/dist/Shell.js +7 -8
- package/dist/Talk.js +3 -3
- package/dist/recent.js +1 -1
- package/dist/rooms.js +2 -2
- package/dist/thread.d.ts +8 -0
- package/dist/thread.js +15 -0
- package/dist/where.d.ts +2 -0
- package/dist/where.js +2 -0
- package/package.json +1 -1
- package/dist/ModeMark.d.ts +0 -8
- package/dist/ModeMark.js +0 -19
package/dist/App.d.ts
CHANGED
|
@@ -1 +1,13 @@
|
|
|
1
|
-
|
|
1
|
+
/**
|
|
2
|
+
* The window's bar, for a host that is a window of its own: `inset` px kept
|
|
3
|
+
* clear at its left for the window's own controls (macOS paints its traffic
|
|
4
|
+
* lights over the page there), and `drag`, the attributes the host's shell
|
|
5
|
+
* reads as the region that moves the window.
|
|
6
|
+
*/
|
|
7
|
+
export interface Bar {
|
|
8
|
+
inset?: number;
|
|
9
|
+
drag?: Record<string, unknown>;
|
|
10
|
+
}
|
|
11
|
+
export declare function App({ bar }?: {
|
|
12
|
+
bar?: Bar;
|
|
13
|
+
}): import("react").JSX.Element;
|
package/dist/App.js
CHANGED
|
@@ -2,58 +2,74 @@
|
|
|
2
2
|
import { Fragment as _Fragment, jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
3
3
|
// The web app as the whole window, in two modes: Chat, a conversation with the
|
|
4
4
|
// product's AI, and Dev, @hanzo/build's `<Builder>` without a rail of its own.
|
|
5
|
-
// One column serves both (Column.tsx): the wordmark and the switch, New, Dev's
|
|
6
|
-
// places, the reader's Recents in both modes, and the account. The address says
|
|
7
|
-
// the mode, as a path (lib/host `talk`, `app`, `place`): `/` and `/chat` are
|
|
8
|
-
// Chat on a new conversation, `/chat/<id>` one of the reader's, and a path under
|
|
9
|
-
// `/dev` is Dev: `/dev/run/<id>` a run, `/dev/projects/<org>/<repo>` a project.
|
|
10
|
-
// Moving between the modes is a history entry (`go`), never a load, so a host
|
|
11
|
-
// serves this one component for every path under each.
|
|
12
5
|
//
|
|
13
|
-
//
|
|
14
|
-
//
|
|
15
|
-
//
|
|
6
|
+
// OUTER the rail (Rail.tsx) and the sidebar beside it (Column.tsx), on the
|
|
7
|
+
// window's floor, with the window's bar above them where the host has
|
|
8
|
+
// no browser around it (`bar`).
|
|
9
|
+
// INNER the content, cut as one rounded panel inset from the window's edges
|
|
10
|
+
// and from the sidebar by the gutter (`--pane-gap`): Chat's
|
|
11
|
+
// conversation with its tabs beside it (Paned), or Dev's builder.
|
|
12
|
+
//
|
|
13
|
+
// The rail moves between the modes; the sidebar holds the mode's name, its
|
|
14
|
+
// search, New, Dev's places and the reader's Recents. The address says the
|
|
15
|
+
// mode, as a path (lib/host `talk`, `app`, `place`): `/` and `/chat` are Chat on
|
|
16
|
+
// a new conversation, `/chat/<id>` one of the reader's, and a path under `/dev`
|
|
17
|
+
// is Dev: `/dev/run/<id>` a run, `/dev/projects/<org>/<repo>` a project. Moving
|
|
18
|
+
// between the modes is a history entry (`go`), never a load, so a host serves
|
|
19
|
+
// this one component for every path under each.
|
|
20
|
+
//
|
|
21
|
+
// Every host draws this: hanzo.ai at `/`, `/chat` and `/dev`, and the desktop
|
|
22
|
+
// app as its main window. The host mounts IamProvider and `<Rooms>` with its
|
|
23
|
+
// router and the path, and draws this only for a signed-in reader; what a
|
|
24
|
+
// stranger sees is the host's.
|
|
25
|
+
//
|
|
26
|
+
// THE SIDEBAR'S TOGGLE IS AT THE WINDOW'S TOP-LEFT. In a browser tab it leads
|
|
27
|
+
// the rail. A host that is a window of its own (`bar`) has no browser around
|
|
28
|
+
// it, so the app draws the window's bar: the toggle, then Back and Forward
|
|
29
|
+
// through the host's router, clear of the window's own controls, and the bar
|
|
30
|
+
// drags the window. Shut, the sidebar leaves the rail alone at the edge; the
|
|
31
|
+
// choice is kept per browser under the key the builder's rail kept it under.
|
|
32
|
+
//
|
|
33
|
+
// THE KEYS: ⌘K / Ctrl+K searches the sidebar (opening it first), ⌘, / Ctrl+,
|
|
34
|
+
// opens Settings, and in a window ⌘[ ⌘] / Alt+← Alt+→ are Back and Forward —
|
|
35
|
+
// never while the reader types.
|
|
16
36
|
//
|
|
17
37
|
// DEV'S PANE HAS NO COLUMN BESIDE IT. The column beside a pane is Chat's (its
|
|
18
|
-
//
|
|
19
|
-
//
|
|
20
|
-
// builder. So Dev's pane is cut on its own and the summary is Chat's alone.
|
|
38
|
+
// tabs, `useOpen().aside`), and that choice outlives a move to Dev, where
|
|
39
|
+
// nothing fills it. So Dev's pane is cut on its own and the tabs are Chat's.
|
|
21
40
|
//
|
|
22
41
|
// EVERY MOVE PUTS THE DRAWER AWAY, the one to where the reader already is
|
|
23
|
-
// included.
|
|
24
|
-
// that closed only on a new address stayed open over the page it had asked for.
|
|
42
|
+
// included.
|
|
25
43
|
//
|
|
26
44
|
// THE PLANS' OWN ADDRESS LEAVES for the pay site in its place (dev.tsx `leave`),
|
|
27
45
|
// so the builder's own plans are never drawn.
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
// column beside it are three panes floating on a lit floor (ground.ts), one
|
|
31
|
-
// gutter round them and between them, `--pane-gap`, each sized by the edge
|
|
32
|
-
// beside it.
|
|
33
|
-
import { useCallback, useEffect, useMemo, useState } from 'react';
|
|
34
|
-
import { Menu } from 'lucide-react';
|
|
46
|
+
import { useCallback, useEffect, useMemo, useRef, useState } from 'react';
|
|
47
|
+
import { ArrowLeft, ArrowRight, Menu } from 'lucide-react';
|
|
35
48
|
import { AiProvider } from '@hanzo/ai/react';
|
|
36
49
|
import { Text, XStack, YStack } from '@hanzo/gui';
|
|
37
|
-
import { Builder, HostProvider, route } from '@hanzo/build';
|
|
50
|
+
import { Builder, HostProvider, path as written, route } from '@hanzo/build';
|
|
38
51
|
import { SidebarIconButton } from '@hanzo/ui/chat';
|
|
39
52
|
import { useAi } from './lib/ai.js';
|
|
40
53
|
import { Chat } from './Chat.js';
|
|
41
54
|
import { Column } from './Column.js';
|
|
42
55
|
import { isPlans, leave, useDevHost } from './dev.js';
|
|
43
56
|
import { useRooms } from './host.js';
|
|
44
|
-
import {
|
|
57
|
+
import { useKept } from './kept.js';
|
|
58
|
+
import { Mark, Rail, Toggle } from './Rail.js';
|
|
45
59
|
import { empty, openThread, useOpen } from './open.js';
|
|
46
60
|
import { Paned } from './Shell.js';
|
|
47
61
|
import { floor, pane } from './ground.js';
|
|
62
|
+
import { pick } from './lib/session.js';
|
|
48
63
|
import { app, go, place, talk } from './lib/host.js';
|
|
64
|
+
import { wordmark } from './where.js';
|
|
49
65
|
/** `/` is Chat on a new conversation. */
|
|
50
66
|
const HOME = { mode: 'chat', thread: null };
|
|
67
|
+
/** The bar's height, px: the reference window's title bar. */
|
|
68
|
+
const BAR = 40;
|
|
51
69
|
/**
|
|
52
70
|
* The dim under the phone's drawer: design's scrim at two fifths, so the page
|
|
53
|
-
* still reads through the drawer's glass
|
|
54
|
-
*
|
|
55
|
-
* carries a `color-mix()` to the page unchanged; its types only name custom
|
|
56
|
-
* properties.
|
|
71
|
+
* still reads through the drawer's glass. gui carries a `color-mix()` to the
|
|
72
|
+
* page unchanged; its types only name custom properties.
|
|
57
73
|
*/
|
|
58
74
|
const DIM = 'color-mix(in srgb, var(--surface-scrim) 40%, transparent)';
|
|
59
75
|
/** The phone's drawer stands the gutter off each edge, or the device's inset where that is wider. */
|
|
@@ -62,11 +78,22 @@ const INSET = {
|
|
|
62
78
|
bottom: 'max(var(--pane-gap), var(--safe-bottom))',
|
|
63
79
|
left: 'max(var(--pane-gap), var(--safe-left))',
|
|
64
80
|
};
|
|
65
|
-
/** Where each mode was last, so the
|
|
81
|
+
/** Where each mode was last, so the rail goes back to it. */
|
|
66
82
|
const last = { chat: '', dev: '' };
|
|
67
|
-
|
|
83
|
+
/** Whether a key press is the reader typing, where the app's keys are the field's. */
|
|
84
|
+
const typing = (e) => {
|
|
85
|
+
const el = e.target;
|
|
86
|
+
return !!el && (el.isContentEditable || el.tagName === 'INPUT' || el.tagName === 'TEXTAREA' || el.tagName === 'SELECT');
|
|
87
|
+
};
|
|
88
|
+
/** The builder's Settings, general section. */
|
|
89
|
+
const SETTINGS = written({ kind: 'settings', section: 'general' });
|
|
90
|
+
export function App({ bar } = {}) {
|
|
68
91
|
const [drawer, setDrawer] = useState(false);
|
|
69
92
|
const shut = useCallback(() => setDrawer(false), []);
|
|
93
|
+
// The sidebar shut, kept per browser under the key the builder's rail kept it under.
|
|
94
|
+
const [closed, setClosed] = useKept('hanzo.build.rail', false);
|
|
95
|
+
// Each ⌘K: the sidebar's search, asked for again.
|
|
96
|
+
const [seek, setSeek] = useState(0);
|
|
70
97
|
const dev = useDevHost();
|
|
71
98
|
// The builder's moves put the drawer away first.
|
|
72
99
|
const host = useMemo(() => ({
|
|
@@ -77,19 +104,22 @@ export function App() {
|
|
|
77
104
|
},
|
|
78
105
|
}), [dev, shut]);
|
|
79
106
|
const { client } = useAi();
|
|
80
|
-
const
|
|
107
|
+
const { router, path } = useRooms();
|
|
108
|
+
const here = path ?? '/';
|
|
81
109
|
const at = place(here) ?? HOME;
|
|
82
110
|
const mode = at.mode;
|
|
111
|
+
const where = at.mode === 'dev' ? at.at : null;
|
|
83
112
|
// A run floats two panes of its own, so the pane it stands in is not cut.
|
|
84
|
-
const run =
|
|
113
|
+
const run = where !== null && route(where).kind === 'run';
|
|
85
114
|
const asked = at.mode === 'chat' ? at.thread : null;
|
|
86
115
|
const { thread } = useOpen();
|
|
87
|
-
const plans =
|
|
116
|
+
const plans = where !== null && isPlans(where);
|
|
88
117
|
useEffect(() => {
|
|
89
118
|
if (plans)
|
|
90
119
|
leave('app');
|
|
91
120
|
}, [plans]);
|
|
92
|
-
// An address names Chat's conversation,
|
|
121
|
+
// An address names Chat's conversation, a conversation just begun writes its
|
|
122
|
+
// id back, and one emptied (New chat, wherever it was pressed) leaves for /chat.
|
|
93
123
|
useEffect(() => {
|
|
94
124
|
if (mode !== 'chat')
|
|
95
125
|
return;
|
|
@@ -100,9 +130,18 @@ export function App() {
|
|
|
100
130
|
// The address decides; the selection follows it.
|
|
101
131
|
// eslint-disable-next-line react-hooks/exhaustive-deps
|
|
102
132
|
}, [mode, asked]);
|
|
133
|
+
// The conversation the pane held before this render: an emptied one is the
|
|
134
|
+
// address's own conversation let go of, never a first render before it opens.
|
|
135
|
+
const held = useRef(null);
|
|
103
136
|
useEffect(() => {
|
|
104
|
-
|
|
137
|
+
const was = held.current;
|
|
138
|
+
held.current = thread;
|
|
139
|
+
if (mode !== 'chat')
|
|
140
|
+
return;
|
|
141
|
+
if (thread && thread !== asked)
|
|
105
142
|
go(talk(thread), true);
|
|
143
|
+
else if (!thread && was && asked === was)
|
|
144
|
+
go(talk());
|
|
106
145
|
// The selection writes the address only when it moves.
|
|
107
146
|
// eslint-disable-next-line react-hooks/exhaustive-deps
|
|
108
147
|
}, [mode, thread]);
|
|
@@ -115,7 +154,49 @@ export function App() {
|
|
|
115
154
|
shut();
|
|
116
155
|
go(address);
|
|
117
156
|
}, [shut]);
|
|
157
|
+
// The app's keys.
|
|
158
|
+
useEffect(() => {
|
|
159
|
+
const key = (e) => {
|
|
160
|
+
const mod = e.metaKey || e.ctrlKey;
|
|
161
|
+
if (mod && !e.shiftKey && !e.altKey && e.key.toLowerCase() === 'k') {
|
|
162
|
+
e.preventDefault();
|
|
163
|
+
setClosed(false);
|
|
164
|
+
if (window.matchMedia('(max-width: 767.98px)').matches)
|
|
165
|
+
setDrawer(true);
|
|
166
|
+
setSeek((n) => n + 1);
|
|
167
|
+
return;
|
|
168
|
+
}
|
|
169
|
+
if (typing(e))
|
|
170
|
+
return;
|
|
171
|
+
if (mod && !e.shiftKey && !e.altKey && e.key === ',') {
|
|
172
|
+
e.preventDefault();
|
|
173
|
+
move(app(SETTINGS));
|
|
174
|
+
return;
|
|
175
|
+
}
|
|
176
|
+
if (!bar)
|
|
177
|
+
return;
|
|
178
|
+
const back = (e.metaKey && e.key === '[') || (e.altKey && e.key === 'ArrowLeft');
|
|
179
|
+
const forth = (e.metaKey && e.key === ']') || (e.altKey && e.key === 'ArrowRight');
|
|
180
|
+
if ((!back && !forth) || e.ctrlKey || e.shiftKey)
|
|
181
|
+
return;
|
|
182
|
+
e.preventDefault();
|
|
183
|
+
if (back)
|
|
184
|
+
router.back();
|
|
185
|
+
else
|
|
186
|
+
router.forward();
|
|
187
|
+
};
|
|
188
|
+
window.addEventListener('keydown', key);
|
|
189
|
+
return () => window.removeEventListener('keydown', key);
|
|
190
|
+
// setClosed is the kept setter of this render; the keys read it fresh each time.
|
|
191
|
+
// eslint-disable-next-line react-hooks/exhaustive-deps
|
|
192
|
+
}, [bar, router, move]);
|
|
118
193
|
const toChat = () => move(last.chat || talk());
|
|
119
194
|
const toDev = () => move(last.dev || app(''));
|
|
120
|
-
|
|
195
|
+
// Another organization: picked, and the app opened in it at the mode's New.
|
|
196
|
+
const org = (o) => {
|
|
197
|
+
pick(o);
|
|
198
|
+
window.location.assign(mode === 'dev' ? app('') : talk());
|
|
199
|
+
};
|
|
200
|
+
const flip = () => setClosed(!closed);
|
|
201
|
+
return (_jsx(AiProvider, { client: client ?? undefined, children: _jsx(HostProvider, { host: host, children: _jsxs(YStack, { "data-slot": "window", ...(bar ? floor : null), flex: 1, minW: 0, height: "100dvh", overflow: "hidden", 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, go: move, onOrg: org }) }), closed ? null : (_jsx(YStack, { "$max-md": { display: 'none' }, children: _jsx(Column, { host: host, mode: mode, thread: thread, go: move, onShut: () => setClosed(true), seek: seek }) })), _jsxs(YStack, { "data-slot": "stage", flex: 1, minW: 0, minH: 0, ml: "var(--pane-gap)", "$max-md": { ml: 0 }, children: [bar ? null : (_jsxs(XStack, { "data-slot": "app-bar", height: 44, px: "$1", gap: "$1", items: "center", shrink: 0, $md: { display: 'none' }, children: [_jsx(SidebarIconButton, { label: "Open sidebar", onPress: () => setDrawer(true), width: 44, height: 44, rounded: 999, children: _jsx(Menu, { size: 18 }) }), _jsx(Text, { fontSize: "$4", fontWeight: "600", color: "$ink", numberOfLines: 1, children: wordmark(mode) })] })), _jsx(XStack, { "data-slot": "panes", flex: 1, minW: 0, minH: 0, children: mode === 'chat' ? (_jsx(Paned, { framed: true, children: _jsx(Chat, {}) })) : (_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, seek: seek, drawer: true, foot: _jsx(Rail, { host: host, mode: mode, at: where, open: true, flat: true, onChat: toChat, onDev: toDev, go: move, onOrg: org }) }) })] })) : null] }) }) }));
|
|
121
202
|
}
|
package/dist/Chat.js
CHANGED
|
@@ -52,13 +52,13 @@ import { useHydrated } from "./lib/hydrated.js";
|
|
|
52
52
|
import { base, served } from "./lib/ai.js";
|
|
53
53
|
import { onServed } from "./lib/served.js";
|
|
54
54
|
import { refused as worded, speech, useDictation, useTalk, useVoice } from "@hanzo/voice";
|
|
55
|
-
import { FileText, Image as ImageMark, Paperclip,
|
|
55
|
+
import { FileText, Image as ImageMark, Paperclip, X } from "lucide-react";
|
|
56
56
|
import { FREE } from "./lib/ai.js";
|
|
57
57
|
import { openThread, useOpen } from "./open.js";
|
|
58
58
|
import { Beside, Framed } from "./Shell.js";
|
|
59
|
-
import { SEED,
|
|
59
|
+
import { SEED, Summary, look } from "./RightPane.js";
|
|
60
60
|
import { Share } from "./Share.js";
|
|
61
|
-
import {
|
|
61
|
+
import { ConversationMenu, ThreadMore, useThreadList } from "./thread.js";
|
|
62
62
|
import { artifacts, lead, nameOf, PAGE, Panel, PanelToggle, prompt, renders, Reply, Served, useDeck, useKey, useMind, usePanel, } from "@hanzo/build";
|
|
63
63
|
import { checkoutUrl } from './lib/pay.js';
|
|
64
64
|
import { Upgrade, useFree } from './Upgrade.js';
|
|
@@ -239,6 +239,8 @@ export function Ask({ placeholder, idle, }) {
|
|
|
239
239
|
* accessible name does not move when the placeholder does.
|
|
240
240
|
*/
|
|
241
241
|
const message = () => `Message ${brand()}`;
|
|
242
|
+
/** The panel offers no kinds of its own in Chat: its tabs are pages. */
|
|
243
|
+
const NONE = [];
|
|
242
244
|
/** The id the room's system turn is held under in the thread. */
|
|
243
245
|
const ROOM = "room";
|
|
244
246
|
/** The models that route a turn rather than name the one that answers it. */
|
|
@@ -489,13 +491,12 @@ function Thread({ placeholder = message(), initial, anonymous = false, heading,
|
|
|
489
491
|
const framed = useContext(Framed);
|
|
490
492
|
const opened = useOpen();
|
|
491
493
|
const { thread: open, open: reopen, agents: room, openAgent, seat, emptied, aside, showAside, showSettings } = opened;
|
|
492
|
-
//
|
|
493
|
-
//
|
|
494
|
-
//
|
|
495
|
-
// archived) reads unpinned.
|
|
494
|
+
// THE HEADER'S ⋯ IS THE ROW'S MENU (thread.tsx): rename, pin, share, its
|
|
495
|
+
// link, a new tab, archive and delete, kept by the server. A conversation not
|
|
496
|
+
// in the list yet (just begun, or archived) has no menu until it is.
|
|
496
497
|
const listing = useThreadList(false, open);
|
|
497
|
-
const acts = useThreadActions();
|
|
498
498
|
const mine = open ? listing.threads.find((t) => t.id === open) : undefined;
|
|
499
|
+
const alone = useMemo(() => new Map(mine ? [[mine.id, mine]] : []), [mine]);
|
|
499
500
|
// The conversation's key in `pane.ts`, the one the column beside it reads.
|
|
500
501
|
const at = channel(opened);
|
|
501
502
|
// The first of them: what a surface that addresses one person reads.
|
|
@@ -1042,8 +1043,16 @@ function Thread({ placeholder = message(), initial, anonymous = false, heading,
|
|
|
1042
1043
|
// record's own picture. Its own answer comes first, which is the order
|
|
1043
1044
|
// `Face` already states for every other surface.
|
|
1044
1045
|
const portrait = mate?.avatar ?? persona?.avatar ?? roleOf(withWhom)?.portrait;
|
|
1045
|
-
//
|
|
1046
|
-
|
|
1046
|
+
// THE PANEL IS TABS OF PAGES, a browser beside the conversation; what the
|
|
1047
|
+
// conversation produced and took in is its Summary (RightPane.tsx), each row
|
|
1048
|
+
// opening here. A deck kept from when Artifacts and Sources were tabs lets them go.
|
|
1049
|
+
useEffect(() => {
|
|
1050
|
+
for (const t of deck.tabs)
|
|
1051
|
+
if (t.kind !== PAGE)
|
|
1052
|
+
deck.shut(t.id);
|
|
1053
|
+
// A sweep of what was kept, once the deck is read.
|
|
1054
|
+
// eslint-disable-next-line react-hooks/exhaustive-deps
|
|
1055
|
+
}, [at]);
|
|
1047
1056
|
/** Whether the field has focus, so the FRAME can wear the ring the field is
|
|
1048
1057
|
* no longer allowed to draw. `:focus-within` would say this in CSS; the
|
|
1049
1058
|
* stylesheet that would hold it cannot reach past gui's own rule, so the
|
|
@@ -1226,7 +1235,7 @@ function Thread({ placeholder = message(), initial, anonymous = false, heading,
|
|
|
1226
1235
|
e.preventDefault();
|
|
1227
1236
|
// A dropped folder arrives as entries; each file under it goes up with its path.
|
|
1228
1237
|
void dropped(e.dataTransfer).then((list) => hold(at, list, via));
|
|
1229
|
-
}, children: [dragging ? (_jsx(YStack, { "aria-hidden": true, position: "absolute", t: 0, r: 0, b: 0, l: 0, z: 10, items: "center", justify: "center", bg: "var(--surface-scrim)", borderWidth: 2, borderStyle: "dashed", borderColor: "$borderColor", rounded: "var(--pane-round)", pointerEvents: "none", children: _jsxs(XStack, { items: "center", gap: "$2", px: "$4", py: "$2.5", rounded: "$10", bg: "$background", borderWidth: 1, borderColor: "$borderColor", children: [_jsx(Paperclip, { size: 15, "aria-hidden": true }), _jsx(Text, { fontSize: "$4", color: "$ink", children: "Drop files to attach" })] }) })) : null, _jsxs(XStack, { height:
|
|
1238
|
+
}, children: [dragging ? (_jsx(YStack, { "aria-hidden": true, position: "absolute", t: 0, r: 0, b: 0, l: 0, z: 10, items: "center", justify: "center", bg: "var(--surface-scrim)", borderWidth: 2, borderStyle: "dashed", borderColor: "$borderColor", rounded: "var(--pane-round)", pointerEvents: "none", children: _jsxs(XStack, { items: "center", gap: "$2", px: "$4", py: "$2.5", rounded: "$10", bg: "$background", borderWidth: 1, borderColor: "$borderColor", children: [_jsx(Paperclip, { size: 15, "aria-hidden": true }), _jsx(Text, { fontSize: "$4", color: "$ink", children: "Drop files to attach" })] }) })) : null, _jsxs(XStack, { "data-slot": "conversation-head", height: 52, items: "center", justify: "space-between", gap: "$3", pl: "$4", pr: "$2", shrink: 0, children: [_jsxs(XStack, { items: "center", gap: "$2", minW: 0, flex: 1, children: [mine ? (_jsx(Text, { fontSize: 15, lineHeight: 20, color: "$ink", numberOfLines: 1, shrink: 1, minW: 0, children: mine.title || "Untitled" })) : null, room.length ? (_jsx(XStack, { role: "list", "aria-label": "Agents in this chat", items: "center", gap: "$1.5", minW: 0, overflowX: "auto", "$platform-web": { scrollbarWidth: "none" }, children: room.map((name) => (_jsxs(XStack, { role: "listitem", items: "center", gap: "$1.5", pl: "$1", pr: "$0.5", py: "$0.5", rounded: 999, borderWidth: 1, borderColor: "$borderColor", shrink: 0, children: [_jsx(Face, { src: faces[name]?.avatar ?? (name === withWhom ? portrait : undefined), emoji: faces[name]?.emoji ?? (name === withWhom ? persona?.emoji : undefined), name: name, size: 22 }), _jsx(Text, { fontSize: "$2", fontWeight: "600", color: "$color", numberOfLines: 1, display: "none", $sm: { display: "flex" }, children: name }), _jsx(Box, { render: "button", onClick: () => openAgent(name), "aria-label": `Remove ${name} from this chat`, title: `Remove ${name}`, width: 24, height: 24, rounded: 999, items: "center", justify: "center", hoverStyle: { bg: "$hover" }, children: _jsx(X, { size: 13, "aria-hidden": true, color: "var(--soft, var(--muted-foreground))" }) })] }, name))) })) : null] }), _jsxs(XStack, { "data-slot": "conversation-controls", items: "center", gap: 2, shrink: 0, children: [open ? _jsx(Share, { thread: open }) : null, mine ? (_jsx(ConversationMenu, { threads: alone, active: open, children: _jsx(XStack, { children: _jsx(ThreadMore, { t: mine }) }) })) : null, _jsx(Summary, { at: at, said: said, held: files, deck: deck, onOpen: () => setPanel(true), onCreate: () => window.location.assign(site("/dev")) }), _jsx(PanelToggle, { open: panel, onToggle: () => setPanel(!panel) })] })] }), wide && aside !== null ? (_jsx(Beside, { children: _jsx(Panel, { side: deck, kinds: NONE, onHide: () => setPanel(false), label: "Tabs" }) })) : null, !wide && panel ? _jsx(Panel, { side: deck, kinds: NONE, sheet: true, onHide: () => setPanel(false), label: "Tabs" }) : null, isFree(model) ? (_jsxs(Text, { fontSize: "$2", color: "$soft", text: "center", maxW: MEASURE, mx: "auto", px: "$4", py: "$2", children: [freeCopy.notice, " ", _jsx(Anchor, { href: site("/legal/privacy"), children: freeCopy.noticeCta })] })) : null, _jsx(ChatSurface, { ...chat, messages: shown, send: guarded,
|
|
1230
1239
|
// AN EMPTY ROOM CENTRES ITS COMPOSER. While nothing has been said the
|
|
1231
1240
|
// opening (the heading and the starters) and the composer sit as one
|
|
1232
1241
|
// group in the middle of the height, with or without the plan bars in
|
package/dist/Column.d.ts
CHANGED
|
@@ -1,15 +1,19 @@
|
|
|
1
1
|
import { type Host } from '@hanzo/build';
|
|
2
|
-
import type
|
|
3
|
-
|
|
2
|
+
import { type ReactNode } from 'react';
|
|
3
|
+
import { type Mode } from './recent.js';
|
|
4
|
+
export declare function Column({ host, mode, thread, go, onShut, seek, drawer, foot, }: {
|
|
4
5
|
host: Host;
|
|
5
6
|
mode: Mode;
|
|
6
7
|
/** The Chat conversation the pane holds, or null. */
|
|
7
8
|
thread: string | null;
|
|
8
|
-
/** The switch: to Chat and to Dev, each where the reader left it. */
|
|
9
|
-
onChat: () => void;
|
|
10
|
-
onDev: () => void;
|
|
11
9
|
/** Moves the app to an address of this page. */
|
|
12
10
|
go: (address: string) => void;
|
|
13
|
-
/**
|
|
11
|
+
/** Shut the sidebar: its edge pulled in past the floor. */
|
|
12
|
+
onShut: () => void;
|
|
13
|
+
/** Each new value asks for the search, focused (⌘K). */
|
|
14
|
+
seek?: number;
|
|
15
|
+
/** Drawn in the phone's drawer: no edge, cut as a floating pane. */
|
|
14
16
|
drawer?: boolean;
|
|
17
|
+
/** What stands at its foot: the drawer's rail, laid flat. */
|
|
18
|
+
foot?: ReactNode;
|
|
15
19
|
}): import("react").JSX.Element;
|
package/dist/Column.js
CHANGED
|
@@ -1,38 +1,39 @@
|
|
|
1
1
|
'use client';
|
|
2
|
-
import {
|
|
3
|
-
// The app's
|
|
4
|
-
//
|
|
5
|
-
//
|
|
2
|
+
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
3
|
+
// The app's sidebar, beside the rail on the window's floor (App.tsx): the
|
|
4
|
+
// mode's name with the bell and the search at its right, New, Dev's places in
|
|
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
7
|
//
|
|
7
|
-
//
|
|
8
|
-
//
|
|
8
|
+
// It is the window, not a panel: no fill, no edge, the floor's own ground. The
|
|
9
|
+
// content beside it is the panel, inset by the gutter.
|
|
10
|
+
//
|
|
11
|
+
// The bell is the org's run activity (@hanzo/build `useSessions`): the runs
|
|
12
|
+
// that moved last, each one press from its page, and Notifications in Settings.
|
|
13
|
+
// The search opens the filter over Recents (⌘K / Ctrl+K from anywhere, `seek`).
|
|
9
14
|
//
|
|
10
|
-
//
|
|
11
|
-
// row
|
|
15
|
+
// Dev's places are @hanzo/build's `nav`, the one list every rail draws, each a
|
|
16
|
+
// row under a quiet group label.
|
|
12
17
|
//
|
|
13
|
-
//
|
|
14
|
-
//
|
|
15
|
-
// `
|
|
16
|
-
// marks, kept under the key the builder's rail kept it under, so a reader's
|
|
17
|
-
// choice outlives the move.
|
|
18
|
+
// Its width is the edge's in the gutter to its right: dragged, keyed or
|
|
19
|
+
// double-clicked back (@hanzo/build's `Grip`), kept per browser. Pulled in past
|
|
20
|
+
// its floor it shuts (`onShut`), as the sidebar's toggle shuts it.
|
|
18
21
|
import { Text, XStack, YStack } from '@hanzo/gui';
|
|
19
|
-
import {
|
|
20
|
-
import { Grip, nav, NEW } from '@hanzo/build';
|
|
21
|
-
import {
|
|
22
|
-
import {
|
|
23
|
-
import {
|
|
24
|
-
import {
|
|
25
|
-
import { useKept } from './kept.js';
|
|
26
|
-
import { Made, Me } from './Me.js';
|
|
22
|
+
import { Bell, Search, Settings2, SquarePen, X } from 'lucide-react';
|
|
23
|
+
import { DOTS, Grip, nav, NEW, path as written, useSessions } from '@hanzo/build';
|
|
24
|
+
import { DropdownMenu } from '@hanzo/ui';
|
|
25
|
+
import { Sidebar, SidebarIconButton, SidebarItem, StatusDot } from '@hanzo/ui/chat';
|
|
26
|
+
import { useEffect, useId, useRef, useState } from 'react';
|
|
27
|
+
import { Made } from './Me.js';
|
|
27
28
|
import { Recents } from './Recents.js';
|
|
28
29
|
import { Setup } from './Setup.js';
|
|
30
|
+
import { ago } from './recent.js';
|
|
29
31
|
import { empty } from './open.js';
|
|
30
32
|
import { useSpan } from './span.js';
|
|
31
33
|
import { pane } from './ground.js';
|
|
32
|
-
import {
|
|
33
|
-
import {
|
|
34
|
-
|
|
35
|
-
/** The grip stands in the gutter between the column and the pane. gui carries a `calc()` to the page unchanged; its
|
|
34
|
+
import { talk } from './lib/host.js';
|
|
35
|
+
import { brand, wordmark } from './where.js';
|
|
36
|
+
/** The grip stands in the gutter between the sidebar and the panel. gui carries a `calc()` to the page unchanged; its
|
|
36
37
|
* types only name custom properties. */
|
|
37
38
|
const GUTTER_END = 'calc(-1 * var(--pane-gap))';
|
|
38
39
|
/** The sidebar fills the column. Its `width` is typed in px alone, though its frame takes any gui size — the type is
|
|
@@ -48,12 +49,10 @@ const FLOAT = {
|
|
|
48
49
|
borderColor: 'var(--border-strong)',
|
|
49
50
|
boxShadow: 'var(--edge-highlight), var(--shadow-floating)',
|
|
50
51
|
};
|
|
51
|
-
/** The
|
|
52
|
-
const WIDTH =
|
|
53
|
-
const FLOOR =
|
|
52
|
+
/** The sidebar's width, px: its default, and the two a drag holds it between. */
|
|
53
|
+
const WIDTH = 240;
|
|
54
|
+
const FLOOR = 200;
|
|
54
55
|
const CEIL = 420;
|
|
55
|
-
/** The collapsed rail's width, px. */
|
|
56
|
-
const RAIL = 60;
|
|
57
56
|
/** Per reader, per browser. */
|
|
58
57
|
const SPAN = 'hanzo.app.column';
|
|
59
58
|
/** Enter and Space press a row the way a click does. */
|
|
@@ -65,19 +64,37 @@ const keys = (press) => ({
|
|
|
65
64
|
press();
|
|
66
65
|
},
|
|
67
66
|
});
|
|
68
|
-
/** A row of the
|
|
69
|
-
const ROW = { rounded: '$4', minH:
|
|
70
|
-
/**
|
|
71
|
-
const
|
|
72
|
-
/**
|
|
73
|
-
const
|
|
74
|
-
|
|
75
|
-
const
|
|
76
|
-
|
|
77
|
-
const
|
|
78
|
-
|
|
67
|
+
/** A row of the sidebar: a pill on a 31px pitch, lit where the reader is. */
|
|
68
|
+
const ROW = { rounded: '$4', minH: 31, py: 0, px: '$2' };
|
|
69
|
+
/** The run activity behind the bell: the runs that moved last, newest first. */
|
|
70
|
+
const RECENT = 6;
|
|
71
|
+
/** A header mark: a 16px glyph in a 32px target. */
|
|
72
|
+
const HEAD = { width: 32, height: 32, rounded: '$3' };
|
|
73
|
+
function Activity({ host }) {
|
|
74
|
+
const store = useSessions(host);
|
|
75
|
+
const latest = [...store.value].sort((a, b) => (a.updatedAt < b.updatedAt ? 1 : -1)).slice(0, RECENT);
|
|
76
|
+
const items = [
|
|
77
|
+
{ type: 'label', key: 'head', label: 'Activity' },
|
|
78
|
+
...(latest.length
|
|
79
|
+
? latest.map((s) => ({
|
|
80
|
+
key: s.id,
|
|
81
|
+
label: s.title || 'Untitled run',
|
|
82
|
+
description: [s.status, ago(s.updatedAt || s.createdAt)].filter(Boolean).join(' · '),
|
|
83
|
+
icon: _jsx(StatusDot, { status: DOTS[s.status] ?? 'idle' }),
|
|
84
|
+
onSelect: () => host.go(s.id),
|
|
85
|
+
}))
|
|
86
|
+
: [{ key: 'none', label: 'Nothing new', disabled: true, onSelect: () => { } }]),
|
|
87
|
+
{ type: 'separator', key: 'line' },
|
|
88
|
+
{ key: 'settings', label: 'Notification settings', icon: _jsx(Settings2, { size: 16, "aria-hidden": true }), onSelect: () => host.go(written({ kind: 'settings', section: 'notifications' })) },
|
|
89
|
+
];
|
|
90
|
+
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 }));
|
|
91
|
+
}
|
|
92
|
+
export function Column({ host, mode, thread, go, onShut, seek = 0, drawer = false, foot, }) {
|
|
79
93
|
const [words, setWords] = useState('');
|
|
80
|
-
// The
|
|
94
|
+
// The search field shows once asked for, and while it holds words.
|
|
95
|
+
const [seeking, setSeeking] = useState(false);
|
|
96
|
+
const field = useRef(null);
|
|
97
|
+
// The desktop sidebar and a phone's drawer can both be in the page: each names its own group labels.
|
|
81
98
|
const uid = useId();
|
|
82
99
|
const span = useSpan(SPAN, WIDTH, FLOOR, CEIL);
|
|
83
100
|
const dev = (p) => host.go(p);
|
|
@@ -88,21 +105,27 @@ export function Column({ host, mode, thread, onChat, onDev, go, drawer = false,
|
|
|
88
105
|
empty();
|
|
89
106
|
go(talk());
|
|
90
107
|
};
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
108
|
+
const ask = () => {
|
|
109
|
+
setSeeking(true);
|
|
110
|
+
requestAnimationFrame(() => field.current?.focus());
|
|
111
|
+
};
|
|
112
|
+
const done = () => {
|
|
113
|
+
setWords('');
|
|
114
|
+
setSeeking(false);
|
|
95
115
|
};
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
116
|
+
useEffect(() => {
|
|
117
|
+
if (seek)
|
|
118
|
+
ask();
|
|
119
|
+
}, [seek]);
|
|
100
120
|
return (_jsxs(YStack, { "data-slot": "app-column", ref: drawer ? undefined : span.ref,
|
|
101
121
|
// inline-style: `--span`, the width a drag rewrites on the element itself (span.ts); no prop sets a variable.
|
|
102
|
-
style: drawer ? undefined : span.style, position: "relative", shrink: 0, height: "100%", width: drawer ?
|
|
122
|
+
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: [_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: mode === 'chat' ? 'Search chats' : 'Search runs', onPress: seeking ? done : ask, "aria-expanded": seeking || !!words, ...HEAD, children: _jsx(Search, { size: 16, "aria-hidden": true }) })] }), seeking || words ? (_jsxs(XStack, { "data-slot": "app-filter", items: "center", gap: "$2", pl: "$3", pr: "$1", height: 36, rounded: 999, borderWidth: 1, borderColor: "$borderColor", bg: "$edge", shrink: 0, mb: "$1.5", children: [_jsx(Search, { size: 15, "aria-hidden": true, color: "var(--soft, var(--muted-foreground))" }), _jsx(Text, { render: _jsx("input", { ref: field, value: words, onChange: (e) => setWords(e.target.value), onKeyDown: (e) => {
|
|
103
123
|
if (e.key === 'Escape')
|
|
104
|
-
|
|
105
|
-
},
|
|
106
|
-
|
|
124
|
+
done();
|
|
125
|
+
}, onBlur: () => {
|
|
126
|
+
if (!words)
|
|
127
|
+
setSeeking(false);
|
|
128
|
+
}, placeholder: mode === 'chat' ? 'Search chats' : 'Search runs', "aria-label": "Filter recents" }), flex: 1, minW: 0, bg: "transparent", borderWidth: 0, outlineStyle: "none", color: "inherit", fontSize: "$3" }), words ? (_jsx(SidebarIconButton, { label: "Clear search", onPress: done, width: 28, height: 28, rounded: 999, children: _jsx(X, { size: 14, "aria-hidden": true }) })) : null] })) : null, 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 ? (_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 ? `${uid}-${g.id}` : undefined, "aria-label": i ? undefined : g.label, 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))) })), _jsx(Recents, { host: host, mode: mode, thread: thread, words: words, onOpen: open, onNew: create }), _jsxs(YStack, { gap: "$2", pt: "$2", shrink: 0, children: [_jsx(Made, {}), _jsx(Setup, {})] }), foot] }), drawer ? null : (_jsx(Grip, { side: "left", span: span.span, floor: FLOOR, ceil: CEIL, reset: WIDTH, onSpan: span.move, onKeep: span.keep, onShut: onShut, label: "Resize sidebar",
|
|
129
|
+
// In the gutter between the sidebar and the panel, not over the sidebar's own rows.
|
|
107
130
|
r: GUTTER_END, width: "var(--pane-gap)" }))] }));
|
|
108
131
|
}
|
package/dist/Home.js
CHANGED
|
@@ -110,12 +110,12 @@ function Open({ at, rooms, back, }) {
|
|
|
110
110
|
return _jsx(Talk, { room: found }, teamKey(found));
|
|
111
111
|
const reading = isLoading || (isAuthenticated && rooms.rooms === null && !rooms.wrong);
|
|
112
112
|
const line = reading
|
|
113
|
-
? 'Reading the
|
|
113
|
+
? 'Reading the channel.'
|
|
114
114
|
: !isAuthenticated
|
|
115
|
-
? 'Sign in to read this
|
|
115
|
+
? 'Sign in to read this channel.'
|
|
116
116
|
: rooms.wrong
|
|
117
|
-
? say(rooms.wrong, 'this
|
|
118
|
-
: 'That
|
|
117
|
+
? say(rooms.wrong, 'this channel')
|
|
118
|
+
: 'That channel is not here.';
|
|
119
119
|
return (_jsxs(YStack, { flex: 1, items: "center", justify: "center", p: "$6", gap: "$3", children: [_jsx(Text, { fontSize: "$3", color: SOFT, children: line }), reading ? null : (_jsx(Box, { render: "button", onClick: back, ...tap, children: _jsx(Text, { fontSize: "$2", fontWeight: "500", color: INK, children: "Back to home" }) }))] }));
|
|
120
120
|
}
|
|
121
121
|
/* ─────────────────────────────────────────────────────────────────────────────
|
|
@@ -151,18 +151,18 @@ function Rooms({ rooms, wrong, open }) {
|
|
|
151
151
|
openRoom(teamKey(made));
|
|
152
152
|
}
|
|
153
153
|
catch (e) {
|
|
154
|
-
setRefused(plainly(e, 'this
|
|
154
|
+
setRefused(plainly(e, 'this channel'));
|
|
155
155
|
}
|
|
156
156
|
finally {
|
|
157
157
|
setOpening(false);
|
|
158
158
|
}
|
|
159
159
|
};
|
|
160
|
-
return (_jsxs(Card, { kind: "rooms", icon: _jsx(Hash, { size: 14, "aria-hidden": true }), title: "
|
|
160
|
+
return (_jsxs(Card, { kind: "rooms", icon: _jsx(Hash, { size: 14, "aria-hidden": true }), title: "Channels", children: [rooms === null ? (_jsx(Waiting, { wrong: wrong, subject: "your channels" })) : rooms.length === 0 ? (_jsx(Quiet, { children: "No channels yet." })) : (_jsx(YStack, { gap: "$1", children: rooms.slice(0, 6).map((one) => (_jsx(Row, { icon: one.direct ? _jsx(AtSign, { size: 12, "aria-hidden": true }) : _jsx(Hash, { size: 12, "aria-hidden": true }), onPress: () => openRoom(teamKey(one)), children: one.name || 'Direct message' }, teamKey(one)))) })), !me ? null : naming ? (_jsxs(YStack, { gap: "$1", children: [_jsxs(XStack, { gap: "$1", items: "center", children: [_jsx(Text, { render: _jsx("input", { value: name, onChange: (e) => setName(e.target.value), onKeyDown: (e) => {
|
|
161
161
|
if (e.key === 'Enter')
|
|
162
162
|
void make();
|
|
163
163
|
if (e.key === 'Escape')
|
|
164
164
|
setNaming(false);
|
|
165
|
-
}, placeholder: "Name", "aria-label": "
|
|
165
|
+
}, placeholder: "Name", "aria-label": "Channel name", autoFocus: true }), flex: 1, minW: 0, bg: "transparent", borderWidth: 1, borderColor: "var(--border)", rounded: 6, px: 8, py: 6, color: "inherit", fontSize: "$2" }), _jsx(Box, { render: "button", onClick: () => void make(), "aria-disabled": opening, opacity: opening ? 0.5 : 1, px: "$2", py: "$1.5", rounded: "$2", borderWidth: 1, borderColor: "$borderColor", hoverStyle: { bg: '$hover' }, children: _jsx(Text, { fontSize: "$2", fontWeight: "500", color: INK, children: "Open it" }) })] }), refused ? (_jsx(Text, { fontSize: "$2", color: BAD, children: refused })) : null] })) : (_jsx(Row, { icon: _jsx(Plus, { size: 12, "aria-hidden": true }), onPress: () => setNaming(true), children: "New room" }))] }));
|
|
166
166
|
}
|
|
167
167
|
/**
|
|
168
168
|
* A read the SDK has no hook for.
|