@hanzo/ui 8.27.19 → 8.27.20

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (98) hide show
  1. package/dist/backends/gui/item.cjs +2 -3
  2. package/dist/backends/gui/item.d.ts.map +1 -1
  3. package/dist/backends/gui/item.js +1 -2
  4. package/dist/backends/gui/item.js.map +1 -1
  5. package/dist/backends/gui/press.cjs +29 -0
  6. package/dist/backends/gui/press.d.ts +28 -0
  7. package/dist/backends/gui/press.d.ts.map +1 -0
  8. package/dist/backends/gui/press.js +26 -0
  9. package/dist/backends/gui/press.js.map +1 -0
  10. package/dist/backends/gui/toggle-group.cjs +2 -3
  11. package/dist/backends/gui/toggle-group.d.ts.map +1 -1
  12. package/dist/backends/gui/toggle-group.js +1 -2
  13. package/dist/backends/gui/toggle-group.js.map +1 -1
  14. package/dist/backends/gui/toggle.cjs +3 -4
  15. package/dist/backends/gui/toggle.d.ts.map +1 -1
  16. package/dist/backends/gui/toggle.js +1 -2
  17. package/dist/backends/gui/toggle.js.map +1 -1
  18. package/dist/catalog/Shelf.cjs +76 -0
  19. package/dist/catalog/Shelf.d.ts +55 -0
  20. package/dist/catalog/Shelf.d.ts.map +1 -0
  21. package/dist/catalog/Shelf.js +74 -0
  22. package/dist/catalog/Shelf.js.map +1 -0
  23. package/dist/catalog/Tile.cjs +72 -0
  24. package/dist/catalog/Tile.d.ts +56 -0
  25. package/dist/catalog/Tile.d.ts.map +1 -0
  26. package/dist/catalog/Tile.js +67 -0
  27. package/dist/catalog/Tile.js.map +1 -0
  28. package/dist/catalog/index.cjs +23 -0
  29. package/dist/catalog/index.d.ts +16 -0
  30. package/dist/catalog/index.d.ts.map +1 -0
  31. package/dist/catalog/index.js +16 -0
  32. package/dist/catalog/index.js.map +1 -0
  33. package/dist/chat/Home.cjs +30 -0
  34. package/dist/chat/Home.d.ts +35 -0
  35. package/dist/chat/Home.d.ts.map +1 -0
  36. package/dist/chat/Home.js +28 -0
  37. package/dist/chat/Home.js.map +1 -0
  38. package/dist/chat/SessionRail.cjs +165 -37
  39. package/dist/chat/SessionRail.d.ts +92 -14
  40. package/dist/chat/SessionRail.d.ts.map +1 -1
  41. package/dist/chat/SessionRail.js +165 -39
  42. package/dist/chat/SessionRail.js.map +1 -1
  43. package/dist/chat/index.cjs +6 -1
  44. package/dist/chat/index.d.ts +2 -1
  45. package/dist/chat/index.d.ts.map +1 -1
  46. package/dist/chat/index.js +2 -1
  47. package/dist/chat/index.js.map +1 -1
  48. package/dist/gallery.cjs +72 -1
  49. package/dist/gallery.d.ts +13 -0
  50. package/dist/gallery.d.ts.map +1 -1
  51. package/dist/gallery.html +45 -3
  52. package/dist/gallery.js +72 -2
  53. package/dist/gallery.js.map +1 -1
  54. package/dist/product/ChipSelect.cjs +2 -3
  55. package/dist/product/ChipSelect.d.ts.map +1 -1
  56. package/dist/product/ChipSelect.js +1 -2
  57. package/dist/product/ChipSelect.js.map +1 -1
  58. package/dist/product/MenuRow.cjs +2 -1
  59. package/dist/product/MenuRow.d.ts +11 -7
  60. package/dist/product/MenuRow.d.ts.map +1 -1
  61. package/dist/product/MenuRow.js +3 -2
  62. package/dist/product/MenuRow.js.map +1 -1
  63. package/dist/product/OrgSwitcher.cjs +16 -5
  64. package/dist/product/OrgSwitcher.d.ts.map +1 -1
  65. package/dist/product/OrgSwitcher.js +16 -5
  66. package/dist/product/OrgSwitcher.js.map +1 -1
  67. package/dist/product/UserMenu.cjs +19 -27
  68. package/dist/product/UserMenu.d.ts +2 -21
  69. package/dist/product/UserMenu.d.ts.map +1 -1
  70. package/dist/product/UserMenu.js +23 -31
  71. package/dist/product/UserMenu.js.map +1 -1
  72. package/dist/product/account.cjs +37 -0
  73. package/dist/product/account.d.ts +55 -0
  74. package/dist/product/account.d.ts.map +1 -0
  75. package/dist/product/account.js +35 -0
  76. package/dist/product/account.js.map +1 -0
  77. package/dist/product/menu/roving.cjs +4 -1
  78. package/dist/product/menu/roving.d.ts +8 -3
  79. package/dist/product/menu/roving.d.ts.map +1 -1
  80. package/dist/product/menu/roving.js +4 -1
  81. package/dist/product/menu/roving.js.map +1 -1
  82. package/dist/settings/Section.cjs +71 -0
  83. package/dist/settings/Section.d.ts +78 -0
  84. package/dist/settings/Section.d.ts.map +1 -0
  85. package/dist/settings/Section.js +62 -0
  86. package/dist/settings/Section.js.map +1 -0
  87. package/dist/settings/Settings.cjs +54 -0
  88. package/dist/settings/Settings.d.ts +50 -0
  89. package/dist/settings/Settings.d.ts.map +1 -0
  90. package/dist/settings/Settings.js +52 -0
  91. package/dist/settings/Settings.js.map +1 -0
  92. package/dist/settings/index.cjs +26 -0
  93. package/dist/settings/index.d.ts +15 -0
  94. package/dist/settings/index.d.ts.map +1 -0
  95. package/dist/settings/index.js +15 -0
  96. package/dist/settings/index.js.map +1 -0
  97. package/dist/styles.css +42 -0
  98. package/package.json +13 -1
@@ -3,43 +3,59 @@
3
3
  Object.defineProperty(exports, "__esModule", { value: true });
4
4
  exports.StatusDot = StatusDot;
5
5
  exports.SessionRail = SessionRail;
6
+ exports.RailNotice = RailNotice;
7
+ exports.RailBar = RailBar;
6
8
  const jsx_runtime_1 = require("react/jsx-runtime");
7
9
  /**
8
- * SessionRail — the left rail of a sessions surface: start one, go somewhere,
9
- * reopen a recent one, and who you are.
10
+ * SessionRail — the left rail of a sessions surface: who it is, find one, start
11
+ * one, go somewhere, reopen a recent one, and who you are.
10
12
  *
13
+ * Hanzo Build the brand; pressing it starts a new session
14
+ * [⌕ Search ] finds a session
11
15
  * + New the primary row; the current view on a fresh pane
12
16
  * Artifacts · Customize … the surface's own places, as `links`
13
17
  * More ⌄ a disclosure over `more`, when there are more
14
18
  * Recents ⇅ the sessions, newest first, each with a status dot
15
- * (z) z@hanzo.ai ⌄ ⚙ ⌕ the account, then settings and search
19
+ * [ Try Hanzo in Slack × ] `notice` — a `RailNotice`, when the host has one
20
+ * (D) Dave ⌄ ◧ the account and its menu, then collapse
21
+ * acme
16
22
  *
17
23
  * Presentational: no routing, no fetching. `active` comes in, `onOpen` goes out.
18
24
  *
19
- * THE SIDEBAR CANON (console, hanzo.app, hanzo.chat, the extension):
25
+ * THE SIDEBAR CANON (console, hanzo.app, hanzo.chat, hanzo.build, the extension):
20
26
  * - Collapse is an EXPLICIT toggle and the host persists it. `collapsed` in,
21
27
  * `onCollapse` out. There is no hover-to-expand and no pin.
22
28
  * - Collapsed is a narrow RAIL that stays on screen — icons only, labels as
23
29
  * tooltips — and its top row is the expand control.
24
- * - Below `md` there is no rail at all. The column hides, and the same
25
- * contents open as a drawer from the left when the host says so (`open`,
26
- * `onOpenChange`) — typically from a menu button only a phone shows.
30
+ * - Below `md` there is no rail at all. The column hides, `RailBar` is the
31
+ * pane's header (menu · brand · search), and its menu opens the same
32
+ * contents as a drawer from the left (`open`, `onOpenChange`). Anything
33
+ * chosen in the drawer closes it: the drawer is the way to a place, not a
34
+ * place.
35
+ *
36
+ * ONE WAY to each thing. Search is the box in the head (an icon on the collapsed
37
+ * rail); settings, usage and signing out are rows of the account menu. There is
38
+ * no second search or settings control at the foot.
27
39
  *
28
40
  * Built from the chat `Sidebar` column and its icon button, so the rail is the
29
- * same material as every other chat surface's.
41
+ * same material as every other chat surface's. The account menu is the same
42
+ * body `UserMenu` draws (`product/account.tsx`), anchored to the account row
43
+ * itself — above it on the open rail, beside it on the collapsed one.
30
44
  */
31
45
  const gui_1 = require("@hanzo/gui");
32
46
  const lucide_icons_2_1 = require("@hanzogui/lucide-icons-2");
33
47
  const react_1 = require("react");
48
+ const button_1 = require("../backends/gui/button.cjs");
49
+ const popover_1 = require("../backends/gui/popover.cjs");
50
+ const press_1 = require("../backends/gui/press.cjs");
34
51
  const sheet_1 = require("../backends/gui/sheet.cjs");
35
52
  const slot_1 = require("../backends/gui/slot.cjs");
53
+ const account_1 = require("../product/account.cjs");
36
54
  const Sidebar_1 = require("./Sidebar.cjs");
37
55
  /** Expanded width, px — the column's own width, its hairline outside it. */
38
56
  const WIDTH = 272;
39
57
  /** Collapsed width, px. The same rail width every chat surface collapses to. */
40
58
  const RAIL = 56;
41
- /** The keyboard focus ring every control in the package draws. */
42
- const RING = { outlineColor: '$outlineColor', outlineWidth: 2, outlineStyle: 'solid' };
43
59
  /** One row: 32px, the 8px radius, icon then label. */
44
60
  const ROW = { minH: 32, px: '$2', rounded: '$3', gap: '$2.5' };
45
61
  /** The status mark: a 6px dot, filled while something is happening. */
@@ -52,46 +68,158 @@ function StatusDot({ status = 'idle' }) {
52
68
  }
53
69
  /** Where a row starts: the 16px column every icon and dot sits centred in. */
54
70
  const Lead = ({ children }) => ((0, jsx_runtime_1.jsx)(gui_1.XStack, { width: 16, height: 16, shrink: 0, items: "center", justify: "center", children: children }));
71
+ /**
72
+ * The surface's name, the head's first line and the phone bar's middle. A
73
+ * press when there is somewhere to go; the words alone otherwise.
74
+ */
75
+ function Brand({ brand, onPress }) {
76
+ const face = typeof brand === 'string' ? ((0, jsx_runtime_1.jsx)(gui_1.SizableText, { size: "$5", fontWeight: "600", color: "$ink", numberOfLines: 1, children: brand })) : (brand);
77
+ if (!onPress)
78
+ return (0, jsx_runtime_1.jsx)(gui_1.XStack, { ...(0, slot_1.slot)('rail-brand'), items: "center", minW: 0, children: face });
79
+ return ((0, jsx_runtime_1.jsx)(gui_1.XStack, { ...(0, slot_1.slot)('rail-brand'), ...(0, press_1.press)(onPress), items: "center", minW: 0, rounded: "$2", cursor: "pointer", focusVisibleStyle: press_1.RING, children: face }));
80
+ }
55
81
  /** One nav row. On the rail it is the icon alone, the label its tooltip and name. */
56
- function Row({ label, icon, onPress, active, rail, quiet, expanded, controls, data = 'rail-row' }) {
57
- return ((0, jsx_runtime_1.jsxs)(gui_1.XStack, { ...(0, slot_1.slot)(data), ...(rail ? (0, slot_1.tip)(label) : null), ...ROW, items: "center", justify: rail ? 'center' : 'flex-start', cursor: "pointer", role: "button", tabIndex: 0, "aria-label": rail ? label : undefined, "aria-current": active ? 'page' : undefined, "aria-expanded": expanded, "aria-controls": controls, bg: active ? '$raised' : undefined, hoverStyle: { bg: active ? '$raised' : '$hover' }, pressStyle: { bg: '$raised' }, focusVisibleStyle: RING, onPress: onPress, onKeyDown: (e) => {
58
- if (e?.key !== 'Enter' && e?.key !== ' ')
59
- return;
60
- e.preventDefault?.();
61
- onPress?.();
62
- }, children: [(0, jsx_runtime_1.jsx)(Lead, { children: icon }), rail ? null : ((0, jsx_runtime_1.jsx)(gui_1.SizableText, { size: "$3", numberOfLines: 1, flex: 1, color: quiet ? '$soft' : '$ink', children: label }))] }));
82
+ function Item({ label, icon, onPress, active, rail, quiet, expanded, controls, data = 'rail-row' }) {
83
+ return ((0, jsx_runtime_1.jsxs)(gui_1.XStack, { ...(0, slot_1.slot)(data), ...(rail ? (0, slot_1.tip)(label) : null), ...(0, press_1.press)(onPress), ...ROW, items: "center", justify: rail ? 'center' : 'flex-start', cursor: "pointer", "aria-label": rail ? label : undefined, "aria-current": active ? 'page' : undefined, "aria-expanded": expanded, "aria-controls": controls, bg: active ? '$raised' : undefined, hoverStyle: { bg: active ? '$raised' : '$hover' }, pressStyle: { bg: '$raised' }, focusVisibleStyle: press_1.RING, children: [(0, jsx_runtime_1.jsx)(Lead, { children: icon }), rail ? null : ((0, jsx_runtime_1.jsx)(gui_1.SizableText, { size: "$3", numberOfLines: 1, flex: 1, color: quiet ? '$soft' : '$ink', children: label }))] }));
63
84
  }
64
- /** The account line: avatar, the name clamped, the caret. */
65
- function Account({ account, rail }) {
66
- const face = ((0, jsx_runtime_1.jsx)(gui_1.XStack, { width: 24, height: 24, rounded: 9999, items: "center", justify: "center", bg: "$raised", overflow: "hidden", shrink: 0, children: account.avatar ?? ((0, jsx_runtime_1.jsx)(gui_1.SizableText, { size: "$1", fontWeight: "600", color: "$ink", children: account.name.charAt(0).toUpperCase() })) }));
67
- return ((0, jsx_runtime_1.jsxs)(gui_1.XStack, { ...(0, slot_1.slot)('rail-account'), ...(rail ? (0, slot_1.tip)(account.name) : null), ...ROW, gap: "$2", px: "$1", flex: rail ? undefined : 1, minW: 0, items: "center", justify: rail ? 'center' : 'flex-start', cursor: account.onPress ? 'pointer' : undefined, role: account.onPress ? 'button' : undefined, tabIndex: account.onPress ? 0 : undefined, "aria-label": account.onPress ? `Account: ${account.name}` : undefined, "aria-haspopup": account.onPress ? 'menu' : undefined, hoverStyle: account.onPress ? { bg: '$hover' } : undefined, onPress: account.onPress, onKeyDown: (e) => {
68
- if (!account.onPress || (e?.key !== 'Enter' && e?.key !== ' '))
69
- return;
70
- e.preventDefault?.();
71
- account.onPress();
72
- }, children: [face, rail ? null : ((0, jsx_runtime_1.jsxs)(jsx_runtime_1.Fragment, { children: [(0, jsx_runtime_1.jsx)(gui_1.SizableText, { size: "$3", numberOfLines: 1, flex: 1, minW: 0, color: "$ink", children: account.name }), account.onPress ? (0, jsx_runtime_1.jsx)(lucide_icons_2_1.ChevronDown, { size: 14, color: "$soft" }) : null] }))] }));
85
+ /** The menu exists when there is something in it to do. */
86
+ const hasMenu = (a) => Boolean(a.onSignOut || a.groups?.some((g) => (Array.isArray(g) ? g : g.items).length));
87
+ /**
88
+ * The account row: avatar, the name over its second line, the caret — and the
89
+ * menu it opens, anchored to the row itself.
90
+ */
91
+ function Who({ account, rail }) {
92
+ const [open, setOpen] = (0, react_1.useState)(false);
93
+ // The row, so closing the menu can hand focus back to it: the popover's own
94
+ // return goes through a trigger ref that an `asChild` stack never fills.
95
+ const row = (0, react_1.useRef)(null);
96
+ const menu = hasMenu(account);
97
+ const acts = menu || Boolean(account.onPress);
98
+ const said = account.sub ? `${account.name} · ${account.sub}` : account.name;
99
+ const face = ((0, jsx_runtime_1.jsx)(gui_1.XStack, { width: 24, height: 24, rounded: 9999, items: "center", justify: "center", bg: "$raised", overflow: "hidden", shrink: 0, "aria-hidden": true, children: account.avatar ?? ((0, jsx_runtime_1.jsx)(gui_1.SizableText, { size: "$1", fontWeight: "600", color: "$ink", children: account.name.charAt(0).toUpperCase() })) }));
100
+ const look = {
101
+ ...(0, slot_1.slot)('rail-account'),
102
+ ...(rail ? (0, slot_1.tip)(said) : null),
103
+ ...ROW,
104
+ gap: '$2',
105
+ px: '$1',
106
+ flex: rail ? undefined : 1,
107
+ minW: 0,
108
+ items: 'center',
109
+ justify: rail ? 'center' : 'flex-start',
110
+ cursor: acts ? 'pointer' : undefined,
111
+ hoverStyle: acts ? { bg: '$hover' } : undefined,
112
+ focusVisibleStyle: acts ? press_1.RING : undefined,
113
+ };
114
+ const body = ((0, jsx_runtime_1.jsxs)(jsx_runtime_1.Fragment, { children: [face, rail ? null : ((0, jsx_runtime_1.jsxs)(jsx_runtime_1.Fragment, { children: [(0, jsx_runtime_1.jsxs)(gui_1.YStack, { flex: 1, minW: 0, children: [(0, jsx_runtime_1.jsx)(gui_1.SizableText, { size: "$3", numberOfLines: 1, color: "$ink", children: account.name }), account.sub ? ((0, jsx_runtime_1.jsx)(gui_1.SizableText, { ...(0, slot_1.slot)('rail-account-sub'), size: "$1", numberOfLines: 1, color: "$soft", children: account.sub })) : null] }), acts ? (0, jsx_runtime_1.jsx)(lucide_icons_2_1.ChevronDown, { size: 14, color: "$soft" }) : null] }))] }));
115
+ if (!menu)
116
+ return account.onPress ? ((0, jsx_runtime_1.jsx)(gui_1.XStack, { ...look, ...(0, press_1.press)(account.onPress), "aria-label": `Account: ${said}`, children: body })) : ((0, jsx_runtime_1.jsx)(gui_1.XStack, { ...look, children: body }));
117
+ return ((0, jsx_runtime_1.jsxs)(popover_1.Popover, { open: open, onOpenChange: setOpen,
118
+ // Above the row on the open rail, where the row spans the column; beside
119
+ // it on the collapsed rail, where above would cover the rail's icons.
120
+ placement: rail ? 'right-end' : 'top-start', allowFlip: true, stayInFrame: true, children: [(0, jsx_runtime_1.jsx)(popover_1.PopoverTrigger, { asChild: true, children: (0, jsx_runtime_1.jsx)(gui_1.XStack, { ref: row, ...look, role: "button", tabIndex: 0, "aria-label": `Account: ${said}`, "aria-haspopup": "menu", "aria-expanded": open,
121
+ // The trigger opens on a press; the keyboard is this row's own (gui
122
+ // does not activate role="button" on Enter).
123
+ onKeyDown: (e) => {
124
+ if (e?.key !== 'Enter' && e?.key !== ' ')
125
+ return;
126
+ e.preventDefault?.();
127
+ setOpen(!open);
128
+ }, children: body }) }), (0, jsx_runtime_1.jsx)(popover_1.PopoverContent, { ...(0, slot_1.slot)('rail-menu'), role: "menu", "aria-label": "Account", sideOffset: 6,
129
+ // A menu's rows run its full width; gui's popper centres children.
130
+ // 260 fits a 390 phone's drawer, and the popper keeps it in frame.
131
+ items: "stretch", width: 260, p: "$1", rounded: "$3", elevation: "$2", onCloseAutoFocus: (e) => {
132
+ e.preventDefault();
133
+ row.current?.focus();
134
+ }, children: (0, jsx_runtime_1.jsx)(account_1.Account, { name: account.name, email: account.email, groups: account.groups, onSignOut: account.onSignOut, signOutLabel: account.signOutLabel, onDone: () => setOpen(false), component: "SessionRail" }) })] }));
73
135
  }
74
136
  /** The rail's contents — the same tree in the column and in the drawer. */
75
- function Contents({ onNew, newLabel = 'New', fresh = false, links = [], more = [], moreLabel = 'More', recents, recentsLabel = 'Recents', active = null, onOpen, onSort, sortLabel = 'Sort and filter', empty, notice, account, onSettings, onSearch, collapsed = false, onCollapse, mark, rail, }) {
137
+ function Contents({ brand, onBrand, onSearch, searchLabel = 'Search', onNew, newLabel = 'New', fresh = false, links = [], more = [], moreLabel = 'More', recents, recentsLabel = 'Recents', active = null, onOpen, onSort, sortLabel = 'Sort and filter', empty, notice, account, collapsed = false, onCollapse, mark, rail, }) {
76
138
  const [unfolded, setUnfolded] = (0, react_1.useState)(false);
77
139
  const id = (0, react_1.useId)();
78
140
  const moreId = `${id}-more`;
79
141
  const headId = `${id}-recents`;
80
- return ((0, jsx_runtime_1.jsxs)(jsx_runtime_1.Fragment, { children: [rail ? ((0, jsx_runtime_1.jsx)(gui_1.XStack, { justify: "center", pb: "$1", children: (0, jsx_runtime_1.jsx)(Sidebar_1.SidebarIconButton, { label: "Expand sidebar", width: 36, height: 36, onPress: () => onCollapse?.(false), children: mark ?? (0, jsx_runtime_1.jsx)(lucide_icons_2_1.PanelLeft, { size: 16 }) }) })) : null, (0, jsx_runtime_1.jsxs)(gui_1.YStack, { ...(0, slot_1.slot)('rail-nav'), gap: "$0", role: "list", children: [onNew ? ((0, jsx_runtime_1.jsx)(gui_1.YStack, { role: "listitem", children: (0, jsx_runtime_1.jsx)(Row, { label: newLabel, icon: (0, jsx_runtime_1.jsx)(lucide_icons_2_1.Plus, { size: 16 }), onPress: onNew, active: fresh, rail: rail, data: "rail-new" }) })) : null, links.map((link) => ((0, jsx_runtime_1.jsx)(gui_1.YStack, { role: "listitem", children: (0, jsx_runtime_1.jsx)(Row, { label: link.label, icon: link.icon, onPress: link.onPress, active: link.active, rail: rail }) }, link.id))), more.length ? ((0, jsx_runtime_1.jsxs)(gui_1.YStack, { role: "listitem", children: [(0, jsx_runtime_1.jsx)(Row, { label: moreLabel, icon: unfolded ? (0, jsx_runtime_1.jsx)(lucide_icons_2_1.ChevronUp, { size: 16 }) : (0, jsx_runtime_1.jsx)(lucide_icons_2_1.ChevronDown, { size: 16 }), onPress: () => setUnfolded((v) => !v), rail: rail, quiet: true, expanded: unfolded, controls: moreId, data: "rail-more" }), unfolded ? ((0, jsx_runtime_1.jsx)(gui_1.YStack, { id: moreId, role: "list", gap: "$0", children: more.map((link) => ((0, jsx_runtime_1.jsx)(gui_1.YStack, { role: "listitem", children: (0, jsx_runtime_1.jsx)(Row, { label: link.label, icon: link.icon, onPress: link.onPress, active: link.active, rail: rail }) }, link.id))) })) : null] })) : null] }), rail ? ((0, jsx_runtime_1.jsx)(gui_1.YStack, { flex: 1 })) : ((0, jsx_runtime_1.jsxs)(gui_1.YStack, { ...(0, slot_1.slot)('rail-recents'), flex: 1, minH: 0, mt: "$5", children: [(0, jsx_runtime_1.jsxs)(gui_1.XStack, { items: "center", justify: "space-between", height: 28, pl: "$2", pr: "$1", shrink: 0, children: [(0, jsx_runtime_1.jsx)(gui_1.SizableText, { id: headId, size: "$2", color: "$soft", children: recentsLabel }), onSort ? ((0, jsx_runtime_1.jsx)(Sidebar_1.SidebarIconButton, { label: sortLabel, onPress: onSort, width: 24, height: 24, children: (0, jsx_runtime_1.jsx)(lucide_icons_2_1.ArrowDownUp, { size: 14 }) })) : null] }), (0, jsx_runtime_1.jsx)(gui_1.ScrollView, { flex: 1, showsVerticalScrollIndicator: false, children: recents.length === 0 ? (empty ? ((0, jsx_runtime_1.jsx)(gui_1.YStack, { px: "$2", py: "$2", children: typeof empty === 'string' ? ((0, jsx_runtime_1.jsx)(gui_1.SizableText, { size: "$2", color: "$soft", children: empty })) : (empty) })) : null) : ((0, jsx_runtime_1.jsx)(gui_1.YStack, { role: "list", "aria-labelledby": headId, gap: 1, children: recents.map((s) => {
142
+ return ((0, jsx_runtime_1.jsxs)(jsx_runtime_1.Fragment, { children: [rail ? ((0, jsx_runtime_1.jsxs)(gui_1.YStack, { items: "center", gap: "$1", pb: "$1", children: [(0, jsx_runtime_1.jsx)(Sidebar_1.SidebarIconButton, { label: "Expand sidebar", width: 36, height: 36, onPress: () => onCollapse?.(false), children: mark ?? (0, jsx_runtime_1.jsx)(lucide_icons_2_1.PanelLeft, { size: 16 }) }), onSearch ? ((0, jsx_runtime_1.jsx)(Item, { label: searchLabel, icon: (0, jsx_runtime_1.jsx)(lucide_icons_2_1.Search, { size: 16 }), onPress: onSearch, rail: true, data: "rail-search" })) : null] })) : brand || onSearch ? ((0, jsx_runtime_1.jsxs)(gui_1.YStack, { ...(0, slot_1.slot)('rail-head'), gap: "$2.5", pt: 2, pb: "$4", shrink: 0, children: [brand ? ((0, jsx_runtime_1.jsx)(gui_1.XStack, { px: "$2.5", children: (0, jsx_runtime_1.jsx)(Brand, { brand: brand, onPress: onBrand ?? onNew }) })) : null, onSearch ? ((0, jsx_runtime_1.jsxs)(gui_1.XStack, { ...(0, slot_1.slot)('rail-search'), ...(0, press_1.press)(onSearch), "aria-label": searchLabel, items: "center", gap: "$2", px: "$2.5", height: 34, rounded: "$3", borderWidth: 1, borderColor: "$borderColor", bg: "$panel", cursor: "pointer", hoverStyle: { bg: '$hover' }, focusVisibleStyle: press_1.RING, children: [(0, jsx_runtime_1.jsx)(lucide_icons_2_1.Search, { size: 15, color: "$soft" }), (0, jsx_runtime_1.jsx)(gui_1.SizableText, { size: "$2", color: "$soft", children: searchLabel })] })) : null] })) : null, (0, jsx_runtime_1.jsxs)(gui_1.YStack, { ...(0, slot_1.slot)('rail-nav'), gap: "$0", role: "list", children: [onNew ? ((0, jsx_runtime_1.jsx)(gui_1.YStack, { role: "listitem", children: (0, jsx_runtime_1.jsx)(Item, { label: newLabel, icon: (0, jsx_runtime_1.jsx)(lucide_icons_2_1.Plus, { size: 16 }), onPress: onNew, active: fresh, rail: rail, data: "rail-new" }) })) : null, links.map((link) => ((0, jsx_runtime_1.jsx)(gui_1.YStack, { role: "listitem", children: (0, jsx_runtime_1.jsx)(Item, { label: link.label, icon: link.icon, onPress: link.onPress, active: link.active, rail: rail }) }, link.id))), more.length ? ((0, jsx_runtime_1.jsxs)(gui_1.YStack, { role: "listitem", children: [(0, jsx_runtime_1.jsx)(Item, { label: moreLabel, icon: unfolded ? (0, jsx_runtime_1.jsx)(lucide_icons_2_1.ChevronUp, { size: 16 }) : (0, jsx_runtime_1.jsx)(lucide_icons_2_1.ChevronDown, { size: 16 }), onPress: () => setUnfolded((v) => !v), rail: rail, quiet: true, expanded: unfolded, controls: moreId, data: "rail-more" }), unfolded ? ((0, jsx_runtime_1.jsx)(gui_1.YStack, { id: moreId, role: "list", gap: "$0", children: more.map((link) => ((0, jsx_runtime_1.jsx)(gui_1.YStack, { role: "listitem", children: (0, jsx_runtime_1.jsx)(Item, { label: link.label, icon: link.icon, onPress: link.onPress, active: link.active, rail: rail }) }, link.id))) })) : null] })) : null] }), rail ? ((0, jsx_runtime_1.jsx)(gui_1.YStack, { flex: 1 })) : ((0, jsx_runtime_1.jsxs)(gui_1.YStack, { ...(0, slot_1.slot)('rail-recents'), flex: 1, minH: 0, mt: "$5", children: [(0, jsx_runtime_1.jsxs)(gui_1.XStack, { items: "center", justify: "space-between", height: 28, pl: "$2", pr: "$1", shrink: 0, children: [(0, jsx_runtime_1.jsx)(gui_1.SizableText, { id: headId, size: "$2", color: "$soft", children: recentsLabel }), onSort ? ((0, jsx_runtime_1.jsx)(Sidebar_1.SidebarIconButton, { label: sortLabel, onPress: onSort, width: 24, height: 24, children: (0, jsx_runtime_1.jsx)(lucide_icons_2_1.ArrowDownUp, { size: 14 }) })) : null] }), (0, jsx_runtime_1.jsx)(gui_1.ScrollView, { flex: 1, showsVerticalScrollIndicator: false, children: recents.length === 0 ? (empty ? ((0, jsx_runtime_1.jsx)(gui_1.YStack, { px: "$2", py: "$2", children: typeof empty === 'string' ? ((0, jsx_runtime_1.jsx)(gui_1.SizableText, { size: "$2", color: "$soft", children: empty })) : (empty) })) : null) : ((0, jsx_runtime_1.jsx)(gui_1.YStack, { role: "list", "aria-labelledby": headId, gap: 1, children: recents.map((s) => {
81
143
  const on = s.id === active;
82
- return ((0, jsx_runtime_1.jsx)(gui_1.YStack, { role: "listitem", children: (0, jsx_runtime_1.jsxs)(gui_1.XStack, { ...(0, slot_1.slot)('rail-session'), ...ROW, items: "center", cursor: "pointer", role: "button", tabIndex: 0, "aria-current": on ? 'page' : undefined, bg: on ? '$raised' : undefined, hoverStyle: { bg: on ? '$raised' : '$hover' }, pressStyle: { bg: '$raised' }, focusVisibleStyle: RING, onPress: () => onOpen(s.id), onKeyDown: (e) => {
83
- if (e?.key !== 'Enter' && e?.key !== ' ')
84
- return;
85
- e.preventDefault?.();
86
- onOpen(s.id);
87
- }, children: [(0, jsx_runtime_1.jsx)(Lead, { children: (0, jsx_runtime_1.jsx)(StatusDot, { status: s.status }) }), (0, jsx_runtime_1.jsx)(gui_1.SizableText, { size: "$3", numberOfLines: 1, flex: 1, minW: 0, color: on ? '$ink' : '$quiet', children: s.title })] }) }, s.id));
88
- }) })) })] })), notice && !rail ? (0, jsx_runtime_1.jsx)(gui_1.YStack, { pt: "$2", children: notice }) : null, (0, jsx_runtime_1.jsxs)(gui_1.XStack, { ...(0, slot_1.slot)('rail-foot'), items: "center", gap: "$1", pt: "$2", mt: "$1", borderTopWidth: 1, borderColor: "$borderColor", flexDirection: rail ? 'column' : 'row', children: [account ? (0, jsx_runtime_1.jsx)(Account, { account: account, rail: rail }) : (0, jsx_runtime_1.jsx)(gui_1.XStack, { flex: 1 }), onSettings ? ((0, jsx_runtime_1.jsx)(Sidebar_1.SidebarIconButton, { label: "Settings", onPress: onSettings, children: (0, jsx_runtime_1.jsx)(lucide_icons_2_1.Settings, { size: 16 }) })) : null, onSearch ? ((0, jsx_runtime_1.jsx)(Sidebar_1.SidebarIconButton, { label: "Search", onPress: onSearch, children: (0, jsx_runtime_1.jsx)(lucide_icons_2_1.Search, { size: 16 }) })) : null, onCollapse && !rail ? ((0, jsx_runtime_1.jsx)(Sidebar_1.SidebarIconButton, { label: "Collapse sidebar", onPress: () => onCollapse(!collapsed), children: (0, jsx_runtime_1.jsx)(lucide_icons_2_1.PanelLeft, { size: 16 }) })) : null] })] }));
144
+ return ((0, jsx_runtime_1.jsx)(gui_1.YStack, { role: "listitem", children: (0, jsx_runtime_1.jsxs)(gui_1.XStack, { ...(0, slot_1.slot)('rail-session'), ...(0, press_1.press)(() => onOpen(s.id)), ...ROW, items: "center", cursor: "pointer", "aria-current": on ? 'page' : undefined, bg: on ? '$raised' : undefined, hoverStyle: { bg: on ? '$raised' : '$hover' }, pressStyle: { bg: '$raised' }, focusVisibleStyle: press_1.RING, children: [(0, jsx_runtime_1.jsx)(Lead, { children: (0, jsx_runtime_1.jsx)(StatusDot, { status: s.status }) }), (0, jsx_runtime_1.jsx)(gui_1.SizableText, { size: "$3", numberOfLines: 1, flex: 1, minW: 0, color: on ? '$ink' : '$quiet', children: s.title })] }) }, s.id));
145
+ }) })) })] })), notice && !rail ? (0, jsx_runtime_1.jsx)(gui_1.YStack, { pt: "$2", children: notice }) : null, (0, jsx_runtime_1.jsxs)(gui_1.XStack, { ...(0, slot_1.slot)('rail-foot'), items: "center", gap: "$1", pt: "$2", mt: "$1", borderTopWidth: 1, borderColor: "$borderColor", flexDirection: rail ? 'column' : 'row', children: [account ? (0, jsx_runtime_1.jsx)(Who, { account: account, rail: rail }) : (0, jsx_runtime_1.jsx)(gui_1.XStack, { flex: 1 }), onCollapse && !rail ? ((0, jsx_runtime_1.jsx)(Sidebar_1.SidebarIconButton, { label: "Collapse sidebar", onPress: () => onCollapse(!collapsed), children: (0, jsx_runtime_1.jsx)(lucide_icons_2_1.PanelLeft, { size: 16 }) })) : null] })] }));
89
146
  }
147
+ /** `run`, then close the drawer — or nothing, when there is nothing to run. */
148
+ const closing = (close) => (run) => run ? (...args) => (close(), run(...args)) : undefined;
90
149
  function SessionRail(props) {
91
150
  const {
92
151
  // Everything the contents read is named here, so none of it reaches the
93
152
  // column as a stray DOM attribute through `rest`.
94
- onNew, newLabel, fresh, links, more, moreLabel, recents, recentsLabel, active, onOpen, onSort, sortLabel, empty, notice, account, onSettings, onSearch, collapsed = false, onCollapse, mark, open = false, onOpenChange, label = 'Sessions', width = WIDTH, ...rest } = props;
153
+ brand, onBrand, onSearch, searchLabel, onNew, newLabel, fresh, links, more, moreLabel, recents, recentsLabel, active, onOpen, onSort, sortLabel, empty, notice, account, collapsed = false, onCollapse, mark, open = false, onOpenChange, label = 'Sessions', width = WIDTH, ...rest } = props;
95
154
  const rail = collapsed;
96
- return ((0, jsx_runtime_1.jsxs)(jsx_runtime_1.Fragment, { children: [(0, jsx_runtime_1.jsx)(Sidebar_1.Sidebar, { ...(0, slot_1.slot)('session-rail'), role: "navigation", "aria-label": label, "data-collapsed": rail ? 'true' : 'false', width: rail ? RAIL : width, gap: "$0", px: rail ? '$1.5' : '$2', py: "$2", "$max-md": { display: 'none' }, ...rest, children: (0, jsx_runtime_1.jsx)(Contents, { ...props, rail: rail }) }), open ? ((0, jsx_runtime_1.jsx)(sheet_1.Sheet, { open: open, onOpenChange: onOpenChange, children: (0, jsx_runtime_1.jsxs)(sheet_1.SheetContent, { side: "left", width: width, maxW: "86%", p: "$2", gap: "$0", bg: "$panel", children: [(0, jsx_runtime_1.jsx)(sheet_1.SheetTitle, { ...(0, slot_1.slot)('session-rail-title'), position: "absolute", opacity: 0, pointerEvents: "none", children: label }), (0, jsx_runtime_1.jsx)(gui_1.YStack, { ...(0, slot_1.slot)('session-rail-drawer'), role: "navigation", "aria-label": label, flex: 1, minH: 0, children: (0, jsx_runtime_1.jsx)(Contents, { ...props, rail: false, collapsed: false, onCollapse: undefined, onNew: onNew ? () => (onOpenChange?.(false), onNew()) : undefined, onOpen: (sid) => (onOpenChange?.(false), onOpen(sid)) }) })] }) })) : null] }));
155
+ // Where focus was when the drawer opened — the phone bar's menu button — and
156
+ // where it goes back to when the drawer closes, by Escape or by a choice. A
157
+ // dialog returns focus to its own trigger, and this drawer has none: the host
158
+ // opens it from `RailBar`, which the dialog cannot see.
159
+ //
160
+ // Read in RENDER, on the one render where `open` turns true — before the
161
+ // drawer exists. gui's Dialog is a native `<dialog>`, and a browser moves focus
162
+ // into one the moment it opens, so by any effect (the focus scope's own
163
+ // `onOpenAutoFocus` included, which then never fires because focus is already
164
+ // inside) the opener is gone. jsdom's `<dialog>` moves nothing, which is why
165
+ // the unit suite could not see this and the consumer suite asserts it.
166
+ const back = (0, react_1.useRef)(null);
167
+ const was = (0, react_1.useRef)(false);
168
+ if (open && !was.current && typeof document !== 'undefined')
169
+ back.current = document.activeElement;
170
+ was.current = open;
171
+ // One way out, said ONCE per gesture. A choice closes the drawer, and the
172
+ // dialog can also report its own dismissal while it unmounts in that same
173
+ // turn; the host hears one `onOpenChange(false)`. The latch lets go after the
174
+ // turn, so a host that keeps the drawer open still hears the next choice.
175
+ const closed = (0, react_1.useRef)(false);
176
+ const close = () => {
177
+ if (closed.current)
178
+ return;
179
+ closed.current = true;
180
+ setTimeout(() => {
181
+ closed.current = false;
182
+ }, 0);
183
+ onOpenChange?.(false);
184
+ };
185
+ const shut = closing(close);
186
+ const relink = (list) => list?.map((l) => ({ ...l, onPress: shut(l.onPress) }));
187
+ return ((0, jsx_runtime_1.jsxs)(jsx_runtime_1.Fragment, { children: [(0, jsx_runtime_1.jsx)(Sidebar_1.Sidebar, { ...(0, slot_1.slot)('session-rail'), role: "navigation", "aria-label": label, "data-collapsed": rail ? 'true' : 'false', width: rail ? RAIL : width, gap: "$0", px: rail ? '$1.5' : '$2', py: "$2", "$max-md": { display: 'none' }, ...rest, children: (0, jsx_runtime_1.jsx)(Contents, { ...props, rail: rail }) }), open ? ((0, jsx_runtime_1.jsx)(sheet_1.Sheet, { open: open, onOpenChange: (next) => (next ? onOpenChange?.(true) : close()), children: (0, jsx_runtime_1.jsxs)(sheet_1.SheetContent, { side: "left", width: width, maxW: "86%", p: "$2", gap: "$0", bg: "$panel", onCloseAutoFocus: (e) => {
188
+ const to = back.current;
189
+ if (!to?.isConnected)
190
+ return;
191
+ e.preventDefault();
192
+ // After the event that closed it: a tap on the overlay dismisses on
193
+ // pointerdown, and the mousedown that follows would blur this
194
+ // focus straight back to the page.
195
+ setTimeout(() => {
196
+ if (to.isConnected)
197
+ to.focus();
198
+ }, 0);
199
+ }, children: [(0, jsx_runtime_1.jsx)(sheet_1.SheetTitle, { ...(0, slot_1.slot)('session-rail-title'), position: "absolute", opacity: 0, pointerEvents: "none", children: label }), (0, jsx_runtime_1.jsx)(gui_1.YStack, { ...(0, slot_1.slot)('session-rail-drawer'), role: "navigation", "aria-label": label, flex: 1, minH: 0, children: (0, jsx_runtime_1.jsx)(Contents, { ...props, rail: false, collapsed: false, onCollapse: undefined, onBrand: shut(onBrand ?? onNew), onSearch: shut(onSearch), onNew: shut(onNew), onOpen: shut(onOpen), links: relink(links), more: relink(more), account: account && {
200
+ ...account,
201
+ onPress: shut(account.onPress),
202
+ onSignOut: shut(account.onSignOut),
203
+ groups: account.groups?.map((g) => Array.isArray(g)
204
+ ? g.map((i) => ({ ...i, onPress: shut(i.onPress) }))
205
+ : { ...g, items: g.items.map((i) => ({ ...i, onPress: shut(i.onPress) })) }),
206
+ } }) })] }) })) : null] }));
207
+ }
208
+ /**
209
+ * RailNotice — a small card at the rail's foot, above the account: an offer
210
+ * ("Try Hanzo in Slack"), its one action, and a way to put it away. Pass it as
211
+ * `SessionRail notice`; the host decides when it shows and remembers a dismissal.
212
+ */
213
+ function RailNotice({ icon, title, action, onAction, onDismiss, dismissLabel = 'Dismiss', ...rest }) {
214
+ const id = (0, react_1.useId)();
215
+ return ((0, jsx_runtime_1.jsxs)(gui_1.XStack, { ...(0, slot_1.slot)('rail-notice'), items: "center", gap: "$2.5", px: "$3", py: "$2.5", rounded: "$3", borderWidth: 1, borderColor: "$borderColor", bg: "$panel", ...rest, children: [icon ? ((0, jsx_runtime_1.jsx)(gui_1.XStack, { shrink: 0, "aria-hidden": true, children: icon })) : null, (0, jsx_runtime_1.jsxs)(gui_1.YStack, { flex: 1, minW: 0, gap: "$0.5", children: [(0, jsx_runtime_1.jsx)(gui_1.SizableText, { id: `${id}-title`, size: "$2", color: "$ink", numberOfLines: 1, children: title }), action && onAction ? ((0, jsx_runtime_1.jsx)(gui_1.XStack, { ...(0, slot_1.slot)('rail-notice-action'), ...(0, press_1.press)(onAction), "aria-describedby": `${id}-title`, self: "flex-start", cursor: "pointer", rounded: "$1", focusVisibleStyle: press_1.RING, children: (0, jsx_runtime_1.jsx)(gui_1.SizableText, { size: "$1", color: "$soft", textDecorationLine: "underline", children: action }) })) : null] }), onDismiss ? ((0, jsx_runtime_1.jsx)(gui_1.XStack, { ...(0, slot_1.slot)('rail-notice-dismiss'), ...(0, press_1.press)(onDismiss), ...(0, slot_1.tip)(dismissLabel), "aria-label": dismissLabel, p: "$1", rounded: "$2", cursor: "pointer", hoverStyle: { bg: '$hover' }, focusVisibleStyle: press_1.RING, children: (0, jsx_runtime_1.jsx)(lucide_icons_2_1.X, { size: 14 }) })) : null] }));
216
+ }
217
+ /**
218
+ * RailBar — the phone's header for a surface with a `SessionRail`: the menu
219
+ * that opens the rail's drawer, the brand, and search on the right. Only below
220
+ * `md`, where there is no rail; from `md` up it draws nothing and the rail's own
221
+ * head carries the same three. Place it at the top of the pane, in flow.
222
+ */
223
+ function RailBar({ onMenu, menuLabel = 'Open menu', brand, onBrand, onSearch, searchLabel = 'Search', ...rest }) {
224
+ return ((0, jsx_runtime_1.jsxs)(gui_1.XStack, { ...(0, slot_1.slot)('rail-bar'), height: 44, px: "$2", gap: "$2", items: "center", shrink: 0, $md: { display: 'none' }, ...rest, children: [(0, jsx_runtime_1.jsx)(button_1.Button, { variant: "ghost", size: "icon-sm", onPress: onMenu, "aria-label": menuLabel, children: (0, jsx_runtime_1.jsx)(lucide_icons_2_1.Menu, { size: 18 }) }), brand ? (0, jsx_runtime_1.jsx)(Brand, { brand: brand, onPress: onBrand }) : null, (0, jsx_runtime_1.jsx)(gui_1.XStack, { flex: 1 }), onSearch ? ((0, jsx_runtime_1.jsx)(button_1.Button, { variant: "ghost", size: "icon-sm", onPress: onSearch, "aria-label": searchLabel, children: (0, jsx_runtime_1.jsx)(lucide_icons_2_1.Search, { size: 18 }) })) : null] }));
97
225
  }
@@ -1,30 +1,44 @@
1
1
  /**
2
- * SessionRail — the left rail of a sessions surface: start one, go somewhere,
3
- * reopen a recent one, and who you are.
2
+ * SessionRail — the left rail of a sessions surface: who it is, find one, start
3
+ * one, go somewhere, reopen a recent one, and who you are.
4
4
  *
5
+ * Hanzo Build the brand; pressing it starts a new session
6
+ * [⌕ Search ] finds a session
5
7
  * + New the primary row; the current view on a fresh pane
6
8
  * Artifacts · Customize … the surface's own places, as `links`
7
9
  * More ⌄ a disclosure over `more`, when there are more
8
10
  * Recents ⇅ the sessions, newest first, each with a status dot
9
- * (z) z@hanzo.ai ⌄ ⚙ ⌕ the account, then settings and search
11
+ * [ Try Hanzo in Slack × ] `notice` — a `RailNotice`, when the host has one
12
+ * (D) Dave ⌄ ◧ the account and its menu, then collapse
13
+ * acme
10
14
  *
11
15
  * Presentational: no routing, no fetching. `active` comes in, `onOpen` goes out.
12
16
  *
13
- * THE SIDEBAR CANON (console, hanzo.app, hanzo.chat, the extension):
17
+ * THE SIDEBAR CANON (console, hanzo.app, hanzo.chat, hanzo.build, the extension):
14
18
  * - Collapse is an EXPLICIT toggle and the host persists it. `collapsed` in,
15
19
  * `onCollapse` out. There is no hover-to-expand and no pin.
16
20
  * - Collapsed is a narrow RAIL that stays on screen — icons only, labels as
17
21
  * tooltips — and its top row is the expand control.
18
- * - Below `md` there is no rail at all. The column hides, and the same
19
- * contents open as a drawer from the left when the host says so (`open`,
20
- * `onOpenChange`) — typically from a menu button only a phone shows.
22
+ * - Below `md` there is no rail at all. The column hides, `RailBar` is the
23
+ * pane's header (menu · brand · search), and its menu opens the same
24
+ * contents as a drawer from the left (`open`, `onOpenChange`). Anything
25
+ * chosen in the drawer closes it: the drawer is the way to a place, not a
26
+ * place.
27
+ *
28
+ * ONE WAY to each thing. Search is the box in the head (an icon on the collapsed
29
+ * rail); settings, usage and signing out are rows of the account menu. There is
30
+ * no second search or settings control at the foot.
21
31
  *
22
32
  * Built from the chat `Sidebar` column and its icon button, so the rail is the
23
- * same material as every other chat surface's.
33
+ * same material as every other chat surface's. The account menu is the same
34
+ * body `UserMenu` draws (`product/account.tsx`), anchored to the account row
35
+ * itself — above it on the open rail, beside it on the collapsed one.
24
36
  */
25
- import { YStack } from '@hanzo/gui';
37
+ import { XStack, YStack } from '@hanzo/gui';
26
38
  import { type ComponentProps, type ReactNode } from 'react';
39
+ import { type UserMenuGroup } from '../product/account.js';
27
40
  type Col = Omit<ComponentProps<typeof YStack>, 'children'>;
41
+ type Row = Omit<ComponentProps<typeof XStack>, 'children'>;
28
42
  /** Where a session is. `running` pulses; finished and idle are an open ring. */
29
43
  export type SessionStatus = 'running' | 'paused' | 'done' | 'stopped' | 'error' | 'idle';
30
44
  export interface RailSession {
@@ -41,14 +55,41 @@ export interface RailLink {
41
55
  active?: boolean;
42
56
  }
43
57
  export interface RailAccount {
44
- /** The line shown — an email or a name. */
58
+ /** The first line — the person's name, or their email. */
45
59
  name: string;
60
+ /** The second line, muted — the organization they act in. */
61
+ sub?: string;
46
62
  /** Avatar slot. Falls back to the first letter of `name`. */
47
63
  avatar?: ReactNode;
48
- /** The caret beside the name: the account menu. */
64
+ /** Heads the menu, under the name, when it is not the name itself. */
65
+ email?: string;
66
+ /**
67
+ * The menu's rows, in groups with a rule between. A group whose every row
68
+ * carries `active` is a choice — a named group, the chosen row checked —
69
+ * which is how an organization switcher is drawn.
70
+ */
71
+ groups?: UserMenuGroup[];
72
+ /** Sign out, at the foot of the menu. */
73
+ onSignOut?: () => void;
74
+ signOutLabel?: string;
75
+ /**
76
+ * With no menu (no `groups`, no `onSignOut`), the row is this one action —
77
+ * a signed-out rail's "Sign in".
78
+ */
49
79
  onPress?: () => void;
50
80
  }
51
81
  export interface SessionRailProps extends Col {
82
+ /**
83
+ * The head's first line — the surface's name. A string is drawn as the
84
+ * wordmark; a node brings its own (and its own accessible name).
85
+ */
86
+ brand?: ReactNode;
87
+ /** Pressing the brand. Starts a new session (`onNew`) unless given. */
88
+ onBrand?: () => void;
89
+ /** Search. The head draws a search box; the collapsed rail an icon. */
90
+ onSearch?: () => void;
91
+ /** The search control's accessible name — "Search runs". */
92
+ searchLabel?: string;
52
93
  /** Starts a new session. */
53
94
  onNew?: () => void;
54
95
  newLabel?: string;
@@ -70,11 +111,10 @@ export interface SessionRailProps extends Col {
70
111
  sortLabel?: string;
71
112
  /** Shown in place of the list when there are no recents. */
72
113
  empty?: ReactNode;
73
- /** Drawn above the account row — a notice, a setup card. */
114
+ /** Drawn above the account row — a `RailNotice`. */
74
115
  notice?: ReactNode;
116
+ /** Who is signed in, and their menu. */
75
117
  account?: RailAccount;
76
- onSettings?: () => void;
77
- onSearch?: () => void;
78
118
  /** Collapsed to the icon rail. The host owns and persists it. */
79
119
  collapsed?: boolean;
80
120
  onCollapse?: (collapsed: boolean) => void;
@@ -93,5 +133,43 @@ export declare function StatusDot({ status }: {
93
133
  status?: SessionStatus;
94
134
  }): import("react").JSX.Element;
95
135
  export declare function SessionRail(props: SessionRailProps): import("react").JSX.Element;
136
+ export interface RailNoticeProps extends Row {
137
+ /** The notice's mark, ~16px. */
138
+ icon?: ReactNode;
139
+ /** What is on offer, one line. */
140
+ title: string;
141
+ /** The action's words — "Set up". */
142
+ action?: string;
143
+ onAction?: () => void;
144
+ /** Dismissing it. The host remembers that it was. */
145
+ onDismiss?: () => void;
146
+ dismissLabel?: string;
147
+ }
148
+ /**
149
+ * RailNotice — a small card at the rail's foot, above the account: an offer
150
+ * ("Try Hanzo in Slack"), its one action, and a way to put it away. Pass it as
151
+ * `SessionRail notice`; the host decides when it shows and remembers a dismissal.
152
+ */
153
+ export declare function RailNotice({ icon, title, action, onAction, onDismiss, dismissLabel, ...rest }: RailNoticeProps): import("react").JSX.Element;
154
+ export interface RailBarProps extends Row {
155
+ /** Opens the rail's drawer. */
156
+ onMenu: () => void;
157
+ /** The menu button's accessible name — "Open runs". */
158
+ menuLabel?: string;
159
+ /** The surface's name, as the rail's head draws it. */
160
+ brand?: ReactNode;
161
+ /** Pressing the brand. */
162
+ onBrand?: () => void;
163
+ /** Search, as the rail's head offers it. */
164
+ onSearch?: () => void;
165
+ searchLabel?: string;
166
+ }
167
+ /**
168
+ * RailBar — the phone's header for a surface with a `SessionRail`: the menu
169
+ * that opens the rail's drawer, the brand, and search on the right. Only below
170
+ * `md`, where there is no rail; from `md` up it draws nothing and the rail's own
171
+ * head carries the same three. Place it at the top of the pane, in flow.
172
+ */
173
+ export declare function RailBar({ onMenu, menuLabel, brand, onBrand, onSearch, searchLabel, ...rest }: RailBarProps): import("react").JSX.Element;
96
174
  export {};
97
175
  //# sourceMappingURL=SessionRail.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"SessionRail.d.ts","sourceRoot":"","sources":["../../src/chat/SessionRail.tsx"],"names":[],"mappings":"AAEA;;;;;;;;;;;;;;;;;;;;;;;GAuBG;AACH,OAAO,EAAmC,MAAM,EAAE,MAAM,YAAY,CAAA;AAUpE,OAAO,EAAmB,KAAK,cAAc,EAAE,KAAK,SAAS,EAAE,MAAM,OAAO,CAAA;AAM5E,KAAK,GAAG,GAAG,IAAI,CAAC,cAAc,CAAC,OAAO,MAAM,CAAC,EAAE,UAAU,CAAC,CAAA;AAc1D,gFAAgF;AAChF,MAAM,MAAM,aAAa,GAAG,SAAS,GAAG,QAAQ,GAAG,MAAM,GAAG,SAAS,GAAG,OAAO,GAAG,MAAM,CAAA;AAExF,MAAM,WAAW,WAAW;IAC1B,EAAE,EAAE,MAAM,CAAA;IACV,KAAK,EAAE,MAAM,CAAA;IACb,MAAM,CAAC,EAAE,aAAa,CAAA;CACvB;AAED,MAAM,WAAW,QAAQ;IACvB,EAAE,EAAE,MAAM,CAAA;IACV,KAAK,EAAE,MAAM,CAAA;IACb,IAAI,EAAE,SAAS,CAAA;IACf,OAAO,CAAC,EAAE,MAAM,IAAI,CAAA;IACpB,sCAAsC;IACtC,MAAM,CAAC,EAAE,OAAO,CAAA;CACjB;AAED,MAAM,WAAW,WAAW;IAC1B,2CAA2C;IAC3C,IAAI,EAAE,MAAM,CAAA;IACZ,6DAA6D;IAC7D,MAAM,CAAC,EAAE,SAAS,CAAA;IAClB,mDAAmD;IACnD,OAAO,CAAC,EAAE,MAAM,IAAI,CAAA;CACrB;AAED,MAAM,WAAW,gBAAiB,SAAQ,GAAG;IAC3C,4BAA4B;IAC5B,KAAK,CAAC,EAAE,MAAM,IAAI,CAAA;IAClB,QAAQ,CAAC,EAAE,MAAM,CAAA;IACjB,0EAA0E;IAC1E,KAAK,CAAC,EAAE,OAAO,CAAA;IACf,2CAA2C;IAC3C,KAAK,CAAC,EAAE,QAAQ,EAAE,CAAA;IAClB,oEAAoE;IACpE,IAAI,CAAC,EAAE,QAAQ,EAAE,CAAA;IACjB,SAAS,CAAC,EAAE,MAAM,CAAA;IAClB,kCAAkC;IAClC,OAAO,EAAE,WAAW,EAAE,CAAA;IACtB,YAAY,CAAC,EAAE,MAAM,CAAA;IACrB,6BAA6B;IAC7B,MAAM,CAAC,EAAE,MAAM,GAAG,IAAI,CAAA;IACtB,MAAM,EAAE,CAAC,EAAE,EAAE,MAAM,KAAK,IAAI,CAAA;IAC5B,0EAA0E;IAC1E,MAAM,CAAC,EAAE,MAAM,IAAI,CAAA;IACnB,SAAS,CAAC,EAAE,MAAM,CAAA;IAClB,4DAA4D;IAC5D,KAAK,CAAC,EAAE,SAAS,CAAA;IACjB,4DAA4D;IAC5D,MAAM,CAAC,EAAE,SAAS,CAAA;IAClB,OAAO,CAAC,EAAE,WAAW,CAAA;IACrB,UAAU,CAAC,EAAE,MAAM,IAAI,CAAA;IACvB,QAAQ,CAAC,EAAE,MAAM,IAAI,CAAA;IACrB,iEAAiE;IACjE,SAAS,CAAC,EAAE,OAAO,CAAA;IACnB,UAAU,CAAC,EAAE,CAAC,SAAS,EAAE,OAAO,KAAK,IAAI,CAAA;IACzC,sFAAsF;IACtF,IAAI,CAAC,EAAE,SAAS,CAAA;IAChB,sCAAsC;IACtC,IAAI,CAAC,EAAE,OAAO,CAAA;IACd,YAAY,CAAC,EAAE,CAAC,IAAI,EAAE,OAAO,KAAK,IAAI,CAAA;IACtC,2BAA2B;IAC3B,KAAK,CAAC,EAAE,MAAM,CAAA;IACd,0BAA0B;IAC1B,KAAK,CAAC,EAAE,MAAM,CAAA;CACf;AAED,uEAAuE;AACvE,wBAAgB,SAAS,CAAC,EAAE,MAAe,EAAE,EAAE;IAAE,MAAM,CAAC,EAAE,aAAa,CAAA;CAAE,+BAmBxE;AA0SD,wBAAgB,WAAW,CAAC,KAAK,EAAE,gBAAgB,+BAqDlD"}
1
+ {"version":3,"file":"SessionRail.d.ts","sourceRoot":"","sources":["../../src/chat/SessionRail.tsx"],"names":[],"mappings":"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"}