Skip to content

Commit de2686e

Browse files
committed
move search to left sidebar
Signed-off-by: Yujong Lee <yujonglee.dev@gmail.com>
1 parent 4918916 commit de2686e

File tree

4 files changed

+175
-26
lines changed

4 files changed

+175
-26
lines changed

apps/desktop/src/shared/main/index.tsx

Lines changed: 14 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -41,7 +41,6 @@ import { TabContentOnboarding, TabItemOnboarding } from "~/onboarding";
4141
import { TabContentPlugin, TabItemPlugin } from "~/plugins";
4242
import { loadPlugins } from "~/plugins/loader";
4343
import { TabContentSearch, TabItemSearch } from "~/search/advanced";
44-
import { Search } from "~/search/components/search";
4544
import { TabContentNote, TabItemNote } from "~/session";
4645
import { useCaretPosition } from "~/session/components/caret-position-context";
4746
import { TabContentSettings, TabItemSettings } from "~/settings";
@@ -151,8 +150,7 @@ function Header({ tabs }: { tabs: Tab[] }) {
151150
action: handleNewNoteAndListen,
152151
},
153152
]);
154-
const [isSearchManuallyExpanded, setIsSearchManuallyExpanded] =
155-
useState(false);
153+
156154
const scrollState = useScrollState(
157155
tabsScrollContainerRef,
158156
regularTabs.length,
@@ -302,27 +300,22 @@ function Header({ tabs }: { tabs: Tab[] }) {
302300
data-tauri-drag-region
303301
className="flex h-full flex-1 items-center justify-between"
304302
>
305-
{!isSearchManuallyExpanded && (
306-
<Button
307-
onClick={isOnboarding ? undefined : handleNewEmptyTab}
308-
onContextMenu={isOnboarding ? undefined : showNewTabMenu}
309-
disabled={isOnboarding}
310-
variant="ghost"
311-
size="icon"
312-
className={cn([
313-
"text-neutral-600",
314-
isOnboarding && "cursor-not-allowed opacity-40",
315-
])}
316-
>
317-
<PlusIcon size={16} />
318-
</Button>
319-
)}
303+
<Button
304+
onClick={isOnboarding ? undefined : handleNewEmptyTab}
305+
onContextMenu={isOnboarding ? undefined : showNewTabMenu}
306+
disabled={isOnboarding}
307+
variant="ghost"
308+
size="icon"
309+
className={cn([
310+
"text-neutral-600",
311+
isOnboarding && "cursor-not-allowed opacity-40",
312+
])}
313+
>
314+
<PlusIcon size={16} />
315+
</Button>
320316

321317
<div className="ml-auto flex h-full items-center gap-1">
322318
<Update />
323-
{!isOnboarding && (
324-
<Search onManualExpandChange={setIsSearchManuallyExpanded} />
325-
)}
326319
</div>
327320
</div>
328321
</div>

apps/desktop/src/sidebar/index.tsx

Lines changed: 11 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@ import {
1313
import { cn } from "@hypr/utils";
1414

1515
import { ProfileSection } from "./profile";
16+
import { SidebarSearchInput } from "./search-input";
1617
import { TimelineView } from "./timeline";
1718
import { ToastArea } from "./toast";
1819

@@ -80,16 +81,23 @@ export function LeftSidebar() {
8081
</div>
8182
</header>
8283

84+
<SidebarSearchInput />
85+
8386
<div className="flex flex-1 flex-col gap-1 overflow-hidden">
8487
<div className="relative min-h-0 flex-1 overflow-hidden">
8588
{leftsidebar.showDevtool ? (
8689
<Suspense fallback={null}>
8790
<DevtoolView />
8891
</Suspense>
89-
) : showSearchResults ? (
90-
<SearchResults />
9192
) : (
92-
<TimelineView />
93+
<>
94+
<div className={showSearchResults ? "h-full" : "hidden"}>
95+
<SearchResults />
96+
</div>
97+
<div className={showSearchResults ? "hidden" : "h-full"}>
98+
<TimelineView />
99+
</div>
100+
</>
93101
)}
94102
{!leftsidebar.showDevtool && (
95103
<ToastArea isProfileExpanded={isProfileExpanded} />
Lines changed: 148 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,148 @@
1+
import { Loader2Icon, SearchIcon, XIcon } from "lucide-react";
2+
import { useEffect, useMemo } from "react";
3+
4+
import { Kbd } from "@hypr/ui/components/ui/kbd";
5+
import { useCmdKeyPressed } from "@hypr/ui/hooks/use-cmd-key-pressed";
6+
import { cn } from "@hypr/utils";
7+
8+
import { useSearch } from "~/search/contexts/ui";
9+
import { useTabs } from "~/store/zustand/tabs";
10+
11+
export function SidebarSearchInput() {
12+
const {
13+
query,
14+
setQuery,
15+
inputRef,
16+
setFocusImpl,
17+
isSearching,
18+
isIndexing,
19+
selectedIndex,
20+
setSelectedIndex,
21+
results,
22+
} = useSearch();
23+
const isCmdPressed = useCmdKeyPressed();
24+
const openNew = useTabs((state) => state.openNew);
25+
26+
const flatResults = useMemo(() => {
27+
if (!results) return [];
28+
return results.groups.flatMap((g) => g.results);
29+
}, [results]);
30+
31+
useEffect(() => {
32+
setFocusImpl(() => {
33+
inputRef.current?.focus();
34+
});
35+
}, [setFocusImpl, inputRef]);
36+
37+
const showLoading = isSearching || isIndexing;
38+
const showShortcut = isCmdPressed && !query;
39+
40+
return (
41+
<div className="relative flex h-8 shrink-0 items-center px-2">
42+
{showLoading ? (
43+
<Loader2Icon
44+
className={cn([
45+
"absolute left-5 h-4 w-4 animate-spin text-neutral-400",
46+
])}
47+
/>
48+
) : (
49+
<SearchIcon
50+
className={cn(["absolute left-5 h-4 w-4 text-neutral-400"])}
51+
/>
52+
)}
53+
<input
54+
ref={inputRef}
55+
type="text"
56+
placeholder="Search anything..."
57+
value={query}
58+
onChange={(e) => setQuery(e.target.value)}
59+
onKeyDown={(e) => {
60+
if (e.key === "Escape") {
61+
if (query.trim()) {
62+
setQuery("");
63+
setSelectedIndex(-1);
64+
} else {
65+
e.currentTarget.blur();
66+
}
67+
}
68+
if (e.key === "Enter" && (e.metaKey || e.ctrlKey) && query.trim()) {
69+
e.preventDefault();
70+
openNew({
71+
type: "search",
72+
state: {
73+
selectedTypes: null,
74+
initialQuery: query.trim(),
75+
},
76+
});
77+
setQuery("");
78+
e.currentTarget.blur();
79+
}
80+
if (e.key === "ArrowDown" && flatResults.length > 0) {
81+
e.preventDefault();
82+
setSelectedIndex(
83+
Math.min(selectedIndex + 1, flatResults.length - 1),
84+
);
85+
}
86+
if (e.key === "ArrowUp" && flatResults.length > 0) {
87+
e.preventDefault();
88+
setSelectedIndex(Math.max(selectedIndex - 1, -1));
89+
}
90+
if (
91+
e.key === "Enter" &&
92+
!e.metaKey &&
93+
!e.ctrlKey &&
94+
selectedIndex >= 0 &&
95+
selectedIndex < flatResults.length
96+
) {
97+
e.preventDefault();
98+
const item = flatResults[selectedIndex];
99+
if (item.type === "session") {
100+
openNew({ type: "sessions", id: item.id });
101+
} else if (item.type === "human") {
102+
openNew({
103+
type: "contacts",
104+
state: {
105+
selected: { type: "person", id: item.id },
106+
},
107+
});
108+
} else if (item.type === "organization") {
109+
openNew({
110+
type: "contacts",
111+
state: {
112+
selected: { type: "organization", id: item.id },
113+
},
114+
});
115+
}
116+
e.currentTarget.blur();
117+
}
118+
}}
119+
className={cn([
120+
"text-sm placeholder:text-sm placeholder:text-neutral-400",
121+
"h-full w-full pl-8",
122+
query ? "pr-8" : showShortcut ? "pr-14" : "pr-4",
123+
"rounded-lg bg-neutral-100",
124+
"focus:bg-neutral-200 focus:outline-hidden",
125+
])}
126+
/>
127+
{query && (
128+
<button
129+
onClick={() => setQuery("")}
130+
className={cn([
131+
"absolute right-5",
132+
"h-4 w-4",
133+
"text-neutral-400 hover:text-neutral-600",
134+
"transition-colors",
135+
])}
136+
aria-label="Clear search"
137+
>
138+
<XIcon className="h-4 w-4" />
139+
</button>
140+
)}
141+
{showShortcut && (
142+
<div className="absolute top-1 right-4">
143+
<Kbd>⌘ K</Kbd>
144+
</div>
145+
)}
146+
</div>
147+
);
148+
}

plugins/tray/src/menu_items/tray_start.rs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,7 @@ impl MenuItemHandler for TrayStart {
1111
const ID: &'static str = "hypr_tray_start";
1212

1313
fn build(app: &AppHandle<tauri::Wry>) -> Result<MenuItemKind<tauri::Wry>> {
14-
let item = MenuItem::with_id(app, Self::ID, "Start a new recording", true, None::<&str>)?;
14+
let item = MenuItem::with_id(app, Self::ID, "Start a new meeting", true, None::<&str>)?;
1515
Ok(MenuItemKind::MenuItem(item))
1616
}
1717

@@ -42,7 +42,7 @@ impl TrayStart {
4242
MenuItem::with_id(
4343
app,
4444
Self::ID,
45-
"Start a new recording",
45+
"Start a new meeting",
4646
!disabled,
4747
None::<&str>,
4848
)

0 commit comments

Comments
 (0)