Skip to content

Commit 9da907b

Browse files
authored
Merge pull request #53 from BotCoder254/feat/event-driven-orchestration
Move desktop search and bell to TopBar
2 parents de5e8aa + 4b04166 commit 9da907b

4 files changed

Lines changed: 73 additions & 47 deletions

File tree

‎src/components/layout/AppShell.tsx‎

Lines changed: 8 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@ import { AnnouncementBanner } from './AnnouncementBanner';
66
import { CommandPalette } from './CommandPalette';
77
import { MobileTopBar } from './MobileTopBar';
88
import { Sidebar } from './Sidebar';
9+
import { TopBar } from './TopBar';
910

1011
/**
1112
* The persistent application shell, mounted once as a layout route for the
@@ -55,7 +56,7 @@ export function AppShell() {
5556
<div className="flex min-h-0 flex-1">
5657
{/* Desktop sidebar */}
5758
<div className="hidden w-[272px] shrink-0 lg:block">
58-
<Sidebar onSearch={openSearch} />
59+
<Sidebar />
5960
</div>
6061

6162
{/* Mobile drawer + backdrop */}
@@ -84,24 +85,22 @@ export function AppShell() {
8485
drawerOpen ? 'translate-x-0' : '-translate-x-full',
8586
)}
8687
>
87-
<Sidebar
88-
onNavigate={() => setDrawerOpen(false)}
89-
onSearch={(seed) => {
90-
setDrawerOpen(false);
91-
openSearch(seed);
92-
}}
93-
/>
88+
<Sidebar onNavigate={() => setDrawerOpen(false)} />
9489
</div>
9590
</div>
9691

97-
{/* Content column: mobile top bar + floating canvas */}
92+
{/* Content column: top bar (desktop) / mobile top bar + floating canvas */}
9893
<div className="flex min-w-0 flex-1 flex-col">
9994
<MobileTopBar
10095
ref={menuButtonRef}
10196
drawerOpen={drawerOpen}
10297
onMenu={() => setDrawerOpen(true)}
10398
onSearch={() => openSearch()}
10499
/>
100+
{/* Desktop header strip on the exposed outer shell: centered search,
101+
bell far right. A shrink-0 sibling of the canvas, so the canvas
102+
gives up exactly its height and stays the only scroll region. */}
103+
<TopBar onSearch={openSearch} />
105104
<div className="flex min-h-0 flex-1 flex-col p-2 pt-0 lg:p-3 lg:pl-0 lg:pt-3">
106105
<main className="min-h-0 flex-1 overflow-y-auto rounded border border-steel/20 bg-canvas">
107106
<div className="mx-auto max-w-6xl p-4 sm:p-6 lg:p-8">

‎src/components/layout/CommandPalette.tsx‎

Lines changed: 11 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -40,10 +40,17 @@ export function CommandPalette({ open, onOpenChange, initialQuery }: CommandPale
4040
const navigate = useNavigate();
4141
const [q, setQ] = useState('');
4242

43-
useHotkeys('mod+k', () => onOpenChange(!open), {
44-
enableOnFormTags: true,
45-
preventDefault: true,
46-
});
43+
useHotkeys(
44+
'mod+k',
45+
() => onOpenChange(!open),
46+
{
47+
enableOnFormTags: true,
48+
preventDefault: true,
49+
},
50+
// Without deps the callback keeps the first render's `open` (false), so
51+
// the shortcut could open the palette but never toggle it closed.
52+
[open, onOpenChange],
53+
);
4754

4855
// Seed (or clear) the query whenever the palette opens; typing in the
4956
// sidebar input while open appends through the same channel.

‎src/components/layout/Sidebar.tsx‎

Lines changed: 3 additions & 34 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,4 @@
1-
import { Search } from 'lucide-react';
21
import { useMe } from '../../features/auth/hooks/useAuth';
3-
import { NotificationBell } from '../../features/notifications/components/NotificationBell';
42
import { Logo } from '../brand/Logo';
53
import { SidebarNav } from './SidebarNav';
64
import { UserFooter } from './UserFooter';
@@ -9,17 +7,16 @@ import { WorkspaceSwitcher } from './WorkspaceSwitcher';
97
interface SidebarProps {
108
/** Called after a nav link is activated (closes the mobile drawer). */
119
onNavigate?: () => void;
12-
/** Opens the command palette, optionally seeded with typed text. */
13-
onSearch: (seed?: string) => void;
1410
}
1511

1612
/**
1713
* The persistent navigation frame: workspace identity + switcher fixed at the
1814
* top, an independently scrollable nav list in the middle, and the user
1915
* footer fixed at the bottom. Rendered both as the desktop rail and inside
20-
* the mobile drawer.
16+
* the mobile drawer. Search and the notification bell live in the shell's
17+
* top bar (TopBar on desktop, MobileTopBar below lg), not here.
2118
*/
22-
export function Sidebar({ onNavigate, onSearch }: SidebarProps) {
19+
export function Sidebar({ onNavigate }: SidebarProps) {
2320
const { data: me } = useMe();
2421
if (!me?.workspace) return null; // guarded by WorkspaceRoute; satisfies types
2522

@@ -29,34 +26,6 @@ export function Sidebar({ onNavigate, onSearch }: SidebarProps) {
2926
<div className="flex items-center gap-1.5">
3027
<Logo size="sm" withWordmark={false} className="shrink-0 px-1 text-charcoal" />
3128
<WorkspaceSwitcher workspace={me.workspace} />
32-
{/* Operational inbox: right edge of the shell header, before the
33-
user's own controls — the "top of the app" position. */}
34-
<div className="shrink-0">
35-
<NotificationBell />
36-
</div>
37-
</div>
38-
{/* Real search input, centered at the top of the sidebar. Focusing
39-
or typing hands off to the floating palette (seeded with the
40-
typed text) — one search surface, so the input itself stays
41-
empty and the palette owns the query. */}
42-
<div className="relative mx-auto mt-3 w-full">
43-
<Search
44-
size={14}
45-
aria-hidden="true"
46-
className="pointer-events-none absolute left-2.5 top-1/2 -translate-y-1/2 text-steel"
47-
/>
48-
<input
49-
type="search"
50-
aria-label="Search workspace"
51-
placeholder="Search…"
52-
value=""
53-
onFocus={() => onSearch()}
54-
onChange={(event) => onSearch(event.target.value)}
55-
className="w-full rounded border border-steel/20 bg-canvas py-1.5 pl-8 pr-14 text-sm text-charcoal transition-colors placeholder:text-steel hover:border-steel/40 focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-primary"
56-
/>
57-
<kbd className="pointer-events-none absolute right-2 top-1/2 -translate-y-1/2 rounded border border-steel/20 bg-surface px-1.5 font-sans text-[11px] text-steel">
58-
Ctrl K
59-
</kbd>
6029
</div>
6130
</div>
6231
<nav aria-label="Workspace" className="mt-4 min-h-0 flex-1 overflow-y-auto px-3 pb-4">

‎src/components/layout/TopBar.tsx‎

Lines changed: 51 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,51 @@
1+
import { Search } from 'lucide-react';
2+
import { NotificationBell } from '../../features/notifications/components/NotificationBell';
3+
4+
interface TopBarProps {
5+
/** Opens the command palette, optionally seeded with typed text. */
6+
onSearch: (seed?: string) => void;
7+
}
8+
9+
const IS_MAC = /Mac|iPhone|iPad/.test(navigator.platform);
10+
11+
/**
12+
* Desktop-only header strip on the outer shell surface, above the floating
13+
* content canvas: workspace search centered, the notification bell pinned to
14+
* the far right. Below lg these controls live in MobileTopBar instead.
15+
*/
16+
export function TopBar({ onSearch }: TopBarProps) {
17+
return (
18+
<div className="hidden shrink-0 grid-cols-[1fr_minmax(0,28rem)_1fr] items-center gap-2 px-3 pt-3 lg:grid">
19+
{/* Left spacer keeps the search truly centered despite the bell. */}
20+
<div aria-hidden="true" />
21+
{/* Real search input, centered on the shell. Focusing or typing hands
22+
off to the floating palette (seeded with the typed text) — one
23+
search surface, so the input itself stays empty and the palette
24+
owns the query. */}
25+
<div className="relative w-full">
26+
<Search
27+
size={14}
28+
aria-hidden="true"
29+
className="pointer-events-none absolute left-2.5 top-1/2 -translate-y-1/2 text-steel"
30+
/>
31+
<input
32+
type="search"
33+
aria-label="Search workspace"
34+
placeholder="Search…"
35+
value=""
36+
onFocus={() => onSearch()}
37+
onChange={(event) => onSearch(event.target.value)}
38+
className="w-full rounded border border-steel/20 bg-canvas py-1.5 pl-8 pr-14 text-sm text-charcoal transition-colors placeholder:text-steel hover:border-steel/40 focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-primary"
39+
/>
40+
<kbd className="pointer-events-none absolute right-2 top-1/2 -translate-y-1/2 rounded border border-steel/20 bg-surface px-1.5 font-sans text-[11px] text-steel">
41+
{IS_MAC ? '⌘ K' : 'Ctrl K'}
42+
</kbd>
43+
</div>
44+
{/* Operational inbox at the far right of the outer shell; the bell's
45+
popover is portaled and end-aligned, so no extra wiring needed. */}
46+
<div className="flex justify-end">
47+
<NotificationBell />
48+
</div>
49+
</div>
50+
);
51+
}

0 commit comments

Comments
 (0)