SessionRail
import { SessionRail, RailBar, RailNotice, StatusDot } from '@hanzo/ui/chat'
The account, and its menu: who, the organization as a choice, the account's places, and signing out. */ function account(org: string, setOrg: (o: string) => void): RailAccount { return { name: 'Dave', sub: org, email: 'dave@acme.test', groups: [ { label: 'Organization', items: ['acme', 'zoo'].map((o) => ({ id: o, label: o, active: o === org, onPress: () => setOrg(o) })), }, [ { id: 'settings', label: 'Settings', icon: <Settings size={16} />, onPress: () => {} }, { id: 'usage', label: 'Usage', icon: <Gauge size={16} />, onPress: () => {} }, { id: 'help', label: 'Get help', icon: <LifeBuoy size={16} />, onPress: () => {} }, ], ], onSignOut: () => {}, signOutLabel: 'Log out', } } /** The rail
The name and a search at the head, New, the surface's places, the recents with their status, a notice, and the account with its menu. Collapse is an explicit toggle the host keeps.
export function Default() {const [active, setActive] = useState<string | null>(null)const [collapsed, setCollapsed] = useState(false)const [org, setOrg] = useState('acme')const [slack, setSlack] = useState(true)return (<XStack height={560}><SessionRail brand="Hanzo Build" onSearch={() => {}} searchLabel="Search runs" onNew={() => setActive(null)} fresh={active === null} links={[ { id: 'artifacts', label: 'Artifacts', icon: <Box size={16} /> }, { id: 'customize', label: 'Customize', icon: <Wrench size={16} /> }, ]} more={[{ id: 'projects', label: 'Projects', icon: <Box size={16} /> }]} recents={RECENTS} active={active} onOpen={setActive} onSort={() => {}} notice={ slack ? ( <RailNotice icon={<Slack size={16} />} title="Try Hanzo in Slack" action="Set up" onAction={() => {}} onDismiss={() => setSlack(false)} /> ) : undefined } account={account(org, setOrg)} collapsed={collapsed} onCollapse={setCollapsed} label="Runs" /></XStack>)}
On a phone
Narrow the window below md: the column goes, the bar over the pane opens the same rail as a drawer (menu, name, search), and anything chosen in the drawer closes it.
Hanzo Build
export function Phone() {const [open, setOpen] = useState(false)const [active, setActive] = useState<string | null>(null)return (<YStack width={390} height={560} borderWidth={1} borderColor="$borderColor"><RailBar onMenu={() => setOpen(true)} menuLabel="Open runs" brand="Hanzo Build" onBrand={() => setActive(null)} onSearch={() => {}} searchLabel="Search runs" /><SessionRail brand="Hanzo Build" onSearch={() => {}} onNew={() => setActive(null)} fresh={active === null} recents={RECENTS} active={active} onOpen={setActive} account={{ name: 'Sign in', onPress: () => {} }} open={open} onOpenChange={setOpen} label="Runs" /></YStack>)}
A notice
An offer, its one action, and a way to put it away. The host remembers the dismissal.
Try Hanzo in Slack
Set up
export function Notice() {return (<YStack width={256}><RailNotice icon={<Slack size={16} />} title="Try Hanzo in Slack" action="Set up" onAction={() => {}} onDismiss={() => {}} /></YStack>)}
Types
export type SessionStatus = 'running' | 'paused' | 'done' | 'stopped' | 'error' | 'idle'
export interface RailSession {id: stringtitle: stringstatus?: SessionStatus}
export interface RailLink {id: stringlabel: stringicon: ReactNodeonPress?: () => void/** This place is the current view. */active?: boolean}
export interface RailAccount {/** The first line — the person's name, or their email. */name: string/** The second line, muted — the organization they act in. */sub?: string/** Avatar slot. Falls back to the first letter of `name`. */avatar?: ReactNode/** Heads the menu, under the name, when it is not the name itself. */email?: string/*** The menu's rows, in groups with a rule between. A group whose every row* carries `active` is a choice — a named group, the chosen row checked —* which is how an organization switcher is drawn.*/groups?: UserMenuGroup[]/** Sign out, at the foot of the menu. */onSignOut?: () => voidsignOutLabel?: string/*** With no menu (no `groups`, no `onSignOut`), the row is this one action —* a signed-out rail's "Sign in".*/onPress?: () => void}
export interface SessionRailProps extends Col {/*** The head's first line — the surface's name. A string is drawn as the* wordmark; a node brings its own (and its own accessible name).*/brand?: ReactNode/** Pressing the brand. Starts a new session (`onNew`) unless given. */onBrand?: () => void/** Search. The head draws a search box; the collapsed rail an icon. */onSearch?: () => void/** The search control's accessible name — "Search runs". */searchLabel?: string/** Starts a new session. */onNew?: () => voidnewLabel?: string/** The fresh pane IS the current view, so New draws as the active row. */fresh?: boolean/** The surface's own places, under New. */links?: RailLink[]/** Behind the "More" disclosure. None, and there is no More row. */more?: RailLink[]moreLabel?: string/** The sessions, newest first. */recents: RailSession[]recentsLabel?: string/** The open session's id. */active?: string | nullonOpen: (id: string) => void/** The sort/filter control beside the Recents label. None, no control. */onSort?: () => voidsortLabel?: string/** Shown in place of the list when there are no recents. */empty?: ReactNode/** Drawn above the account row — a `RailNotice`. */notice?: ReactNode/** Who is signed in, and their menu. */account?: RailAccount/** Collapsed to the icon rail. The host owns and persists it. */collapsed?: booleanonCollapse?: (collapsed: boolean) => void/** The rail's top row when collapsed — a surface's mark. It is the expand control. */mark?: ReactNode/** Below `md`: the drawer is open. */open?: booleanonOpenChange?: (open: boolean) => void/** The landmark's name. */label?: string/** Expanded width, px. */width?: number}
export interface RailNoticeProps extends Row {/** The notice's mark, ~16px. */icon?: ReactNode/** What is on offer, one line. */title: string/** The action's words — "Set up". */action?: stringonAction?: () => void/** Dismissing it. The host remembers that it was. */onDismiss?: () => voiddismissLabel?: string}
export interface RailBarProps extends Row {/** Opens the rail's drawer. */onMenu: () => void/** The menu button's accessible name — "Open runs". */menuLabel?: string/** The surface's name, as the rail's head draws it. */brand?: ReactNode/** Pressing the brand. */onBrand?: () => void/** Search, as the rail's head offers it. */onSearch?: () => voidsearchLabel?: string}
Exports
SessionRailRailBarRailNoticeStatusDottype RailAccounttype RailBarPropstype RailLinktype RailNoticePropstype RailSessiontype SessionRailPropstype SessionStatus