+
{children}
{floatingButton}
@@ -714,6 +651,7 @@ function useScrollState(
const [scrollState, setScrollState] = useState({
atStart: true,
atEnd: true,
+ contentOverflowPx: 0,
});
const updateScrollState = useCallback(() => {
@@ -722,12 +660,21 @@ function useScrollState(
const { scrollLeft, scrollWidth, clientWidth } = container;
const hasOverflow = scrollWidth > clientWidth + 1;
+ const contentWidth = Array.from(container.children).reduce(
+ (sum, child) => sum + child.getBoundingClientRect().width,
+ 0,
+ );
const newState = {
atStart: !hasOverflow || scrollLeft <= 1,
atEnd: !hasOverflow || scrollLeft + clientWidth >= scrollWidth - 1,
+ contentOverflowPx: Math.round(contentWidth - clientWidth),
};
setScrollState((prev) => {
- if (prev.atStart === newState.atStart && prev.atEnd === newState.atEnd) {
+ if (
+ prev.atStart === newState.atStart &&
+ prev.atEnd === newState.atEnd &&
+ prev.contentOverflowPx === newState.contentOverflowPx
+ ) {
return prev;
}
return newState;
diff --git a/apps/desktop/src/shared/tabs.tsx b/apps/desktop/src/shared/tabs.tsx
index ec8805c8fe..b80017c37d 100644
--- a/apps/desktop/src/shared/tabs.tsx
+++ b/apps/desktop/src/shared/tabs.tsx
@@ -37,8 +37,18 @@ const accentColors: Record<
}
> = {
neutral: {
- selected: ["bg-neutral-50", "text-black", "border-stone-400"],
- unselected: ["bg-neutral-50", "text-neutral-500", "border-transparent"],
+ selected: [
+ "bg-neutral-50",
+ "text-black",
+ "border-stone-400",
+ "hover:bg-stone-100",
+ ],
+ unselected: [
+ "bg-neutral-50",
+ "text-neutral-500",
+ "border-transparent",
+ "hover:bg-stone-100",
+ ],
hover: {
selected: "text-neutral-700 hover:text-neutral-900",
unselected: "text-neutral-500 hover:text-neutral-700",
diff --git a/apps/desktop/src/sidebar/index.tsx b/apps/desktop/src/sidebar/index.tsx
index cc3fa8f36b..6665408b80 100644
--- a/apps/desktop/src/sidebar/index.tsx
+++ b/apps/desktop/src/sidebar/index.tsx
@@ -1,7 +1,13 @@
import { useQuery } from "@tanstack/react-query";
import { platform } from "@tauri-apps/plugin-os";
-import { AxeIcon, PanelLeftCloseIcon } from "lucide-react";
-import { lazy, Suspense, useState } from "react";
+import {
+ AxeIcon,
+ Loader2Icon,
+ PanelLeftCloseIcon,
+ SearchIcon,
+ XIcon,
+} from "lucide-react";
+import { lazy, Suspense, useMemo, useRef, useState } from "react";
import { Button } from "@hypr/ui/components/ui/button";
import { Kbd } from "@hypr/ui/components/ui/kbd";
@@ -20,6 +26,7 @@ import { useShell } from "~/contexts/shell";
import { SearchResults } from "~/search/components/sidebar";
import { useSearch } from "~/search/contexts/ui";
import { TrafficLights } from "~/shared/ui/traffic-lights";
+import { useTabs } from "~/store/zustand/tabs";
import { commands } from "~/types/tauri.gen";
const DevtoolView = lazy(() =>
@@ -48,7 +55,6 @@ export function LeftSidebar() {
"h-9 w-full py-1",
isLinux ? "justify-between pl-3" : "justify-end pl-20",
"shrink-0",
- "rounded-xl bg-neutral-50",
])}
>
{isLinux && }
@@ -80,6 +86,8 @@ export function LeftSidebar() {
+
+
{leftsidebar.showDevtool ? (
@@ -102,3 +110,126 @@ export function LeftSidebar() {
);
}
+
+function SidebarSearchInput() {
+ const {
+ query,
+ setQuery,
+ isSearching,
+ isIndexing,
+ inputRef,
+ results,
+ selectedIndex,
+ setSelectedIndex,
+ } = useSearch();
+ const openNew = useTabs((state) => state.openNew);
+ const inputLocalRef = useRef
(null);
+
+ const flatResults = useMemo(() => {
+ if (!results) return [];
+ return results.groups.flatMap((g) => g.results);
+ }, [results]);
+
+ const showLoading = isSearching || isIndexing;
+
+ const ref = inputRef ?? inputLocalRef;
+
+ return (
+
+
+ {showLoading ? (
+
+ ) : (
+
+ )}
+ setQuery(e.target.value)}
+ onKeyDown={(e) => {
+ if (e.key === "Escape") {
+ if (query.trim()) {
+ setQuery("");
+ setSelectedIndex(-1);
+ } else {
+ e.currentTarget.blur();
+ }
+ }
+ if (e.key === "Enter" && (e.metaKey || e.ctrlKey) && query.trim()) {
+ e.preventDefault();
+ openNew({
+ type: "search",
+ state: {
+ selectedTypes: null,
+ initialQuery: query.trim(),
+ },
+ });
+ setQuery("");
+ e.currentTarget.blur();
+ }
+ if (e.key === "ArrowDown" && flatResults.length > 0) {
+ e.preventDefault();
+ setSelectedIndex(
+ Math.min(selectedIndex + 1, flatResults.length - 1),
+ );
+ }
+ if (e.key === "ArrowUp" && flatResults.length > 0) {
+ e.preventDefault();
+ setSelectedIndex(Math.max(selectedIndex - 1, -1));
+ }
+ if (
+ e.key === "Enter" &&
+ !e.metaKey &&
+ !e.ctrlKey &&
+ selectedIndex >= 0 &&
+ selectedIndex < flatResults.length
+ ) {
+ e.preventDefault();
+ const item = flatResults[selectedIndex];
+ if (item.type === "session") {
+ openNew({ type: "sessions", id: item.id });
+ } else if (item.type === "human") {
+ openNew({
+ type: "contacts",
+ state: {
+ selected: { type: "person", id: item.id },
+ },
+ });
+ } else if (item.type === "organization") {
+ openNew({
+ type: "contacts",
+ state: {
+ selected: { type: "organization", id: item.id },
+ },
+ });
+ }
+ e.currentTarget.blur();
+ }
+ }}
+ className={cn([
+ "min-w-0 flex-1 bg-transparent text-sm",
+ "placeholder:text-neutral-400",
+ "focus:outline-hidden",
+ ])}
+ />
+ {query && (
+
+ )}
+ {!query && ⌘ K}
+
+
+ );
+}
diff --git a/apps/desktop/src/sidebar/profile/index.tsx b/apps/desktop/src/sidebar/profile/index.tsx
index e5b6647864..6f0eeba9de 100644
--- a/apps/desktop/src/sidebar/profile/index.tsx
+++ b/apps/desktop/src/sidebar/profile/index.tsx
@@ -212,7 +212,7 @@ export function ProfileSection({ onExpandChange }: ProfileSectionProps = {}) {
transition={{ duration: 0.2, ease: "easeInOut" }}
className="absolute right-0 bottom-full left-0 mb-1"
>
-
+
{currentView === "main" ? (
@@ -269,7 +269,7 @@ export function ProfileSection({ onExpandChange }: ProfileSectionProps = {}) {
)}
-
+
setIsExpanded(!isExpanded)}
@@ -316,8 +316,8 @@ function ProfileButton({
"px-4 py-2",
"text-left",
"transition-all duration-300",
- "hover:bg-neutral-100",
- isExpanded && "border-t border-neutral-100 bg-neutral-50",
+ "rounded-lg hover:bg-neutral-200/50",
+ isExpanded && "border-neutral-300 bg-neutral-200/50",
])}
onClick={onClick}
>
diff --git a/apps/desktop/src/sidebar/timeline/index.tsx b/apps/desktop/src/sidebar/timeline/index.tsx
index 81d0619420..2dfdefc0be 100644
--- a/apps/desktop/src/sidebar/timeline/index.tsx
+++ b/apps/desktop/src/sidebar/timeline/index.tsx
@@ -210,10 +210,7 @@ export function TimelineView() {
{buckets.map((bucket, index) => {
const isToday = bucket.label === "Today";
@@ -225,12 +222,7 @@ export function TimelineView() {
{shouldRenderIndicatorBefore && (
)}
-
+
{bucket.label}
diff --git a/apps/desktop/src/sidebar/timeline/item.tsx b/apps/desktop/src/sidebar/timeline/item.tsx
index c1d783e04c..f951857d05 100644
--- a/apps/desktop/src/sidebar/timeline/item.tsx
+++ b/apps/desktop/src/sidebar/timeline/item.tsx
@@ -113,7 +113,7 @@ function ItemBase({
"w-full cursor-pointer rounded-lg px-3 py-2 text-left",
multiSelected && "bg-neutral-200",
!multiSelected && selected && "bg-neutral-200",
- !multiSelected && !selected && "hover:bg-neutral-100",
+ !multiSelected && !selected && "hover:bg-neutral-200/50",
ignored && "opacity-40",
])}
>
diff --git a/apps/desktop/src/store/zustand/tabs/basic.ts b/apps/desktop/src/store/zustand/tabs/basic.ts
index 73720f42fe..1a0deaf7e5 100644
--- a/apps/desktop/src/store/zustand/tabs/basic.ts
+++ b/apps/desktop/src/store/zustand/tabs/basic.ts
@@ -22,7 +22,7 @@ export type BasicState = {
export type BasicActions = {
openCurrent: (tab: TabInput) => void;
- openNew: (tab: TabInput) => void;
+ openNew: (tab: TabInput, options?: { position?: "start" | "end" }) => void;
select: (tab: Tab) => void;
selectNext: () => void;
selectPrev: () => void;
@@ -72,9 +72,9 @@ export const createBasicSlice = <
view: tab.type,
});
},
- openNew: (tab) => {
+ openNew: (tab, options) => {
const { tabs, history, addRecentlyOpened } = get();
- set(openTab(tabs, tab, history, true));
+ set(openTab(tabs, tab, history, true, options?.position));
if (tab.type === "sessions") {
addRecentlyOpened(tab.id);
@@ -271,6 +271,7 @@ const openTab =
(
newTab: TabInput,
history: Map,
forceNewTab: boolean,
+ position?: "start" | "end",
): Partial => {
const tabWithDefaults: Tab = {
...getDefaultState(newTab),
@@ -317,7 +318,17 @@ const openTab = (
} else {
activeTab = { ...tabWithDefaults, active: true, slotId: id() };
const deactivated = deactivateAll(tabs);
- nextTabs = [...deactivated, activeTab];
+
+ if (position === "start") {
+ const pinnedCount = deactivated.filter((t) => t.pinned).length;
+ nextTabs = [
+ ...deactivated.slice(0, pinnedCount),
+ activeTab,
+ ...deactivated.slice(pinnedCount),
+ ];
+ } else {
+ nextTabs = [...deactivated, activeTab];
+ }
return updateWithHistory(nextTabs, activeTab, history);
}
diff --git a/plugins/tray/src/menu_items/tray_start.rs b/plugins/tray/src/menu_items/tray_start.rs
index a5a0348de5..ec481704a3 100644
--- a/plugins/tray/src/menu_items/tray_start.rs
+++ b/plugins/tray/src/menu_items/tray_start.rs
@@ -11,7 +11,7 @@ impl MenuItemHandler for TrayStart {
const ID: &'static str = "hypr_tray_start";
fn build(app: &AppHandle) -> Result> {
- let item = MenuItem::with_id(app, Self::ID, "Start a new recording", true, None::<&str>)?;
+ let item = MenuItem::with_id(app, Self::ID, "Start a new meeting", true, None::<&str>)?;
Ok(MenuItemKind::MenuItem(item))
}
@@ -42,7 +42,7 @@ impl TrayStart {
MenuItem::with_id(
app,
Self::ID,
- "Start a new recording",
+ "Start a new meeting",
!disabled,
None::<&str>,
)