diff --git a/apps/web/src/components/settings/SettingsPanels.tsx b/apps/web/src/components/settings/SettingsPanels.tsx index ea874c2b18c30..d8b3cb912b40b 100644 --- a/apps/web/src/components/settings/SettingsPanels.tsx +++ b/apps/web/src/components/settings/SettingsPanels.tsx @@ -84,6 +84,10 @@ import { Button } from "../ui/button"; import { DraftInput } from "../ui/draft-input"; import { Input } from "../ui/input"; import { Select, SelectItem, SelectPopup, SelectTrigger, SelectValue } from "../ui/select"; +import { + isSidebarProjectSortOrder, + SIDEBAR_PROJECT_SORT_ORDER_LABELS, +} from "../sidebar/SidebarThreadHeader"; import { Switch } from "../ui/switch"; import { ScopedSwitch } from "./ScopedSwitch"; import { stackedThreadToast, toastManager } from "../ui/toast"; @@ -137,13 +141,6 @@ const RESPONSE_STREAMING_MODE_DESCRIPTIONS: Record = { - updated_at: msg("Last user message"), - created_at: msg("Created at"), - manual: msg("Manual"), -}; -const isSidebarProjectSortOrder = Schema.is(SidebarProjectSortOrder); - const TIMESTAMP_FORMAT_LABELS = { locale: msg("System default"), "12-hour": msg("12-hour"), diff --git a/apps/web/src/components/sidebar/SidebarThreadHeader.tsx b/apps/web/src/components/sidebar/SidebarThreadHeader.tsx index e400e03d27586..4a03ad429833c 100644 --- a/apps/web/src/components/sidebar/SidebarThreadHeader.tsx +++ b/apps/web/src/components/sidebar/SidebarThreadHeader.tsx @@ -1,4 +1,7 @@ -import { FolderPlusIcon, SearchIcon, SquarePenIcon, XIcon } from "lucide-react"; +import { SidebarProjectSortOrder } from "@t3tools/contracts/settings"; +import * as Schema from "effect/Schema"; +import { msg, useMessages } from "gt-react"; +import { ArrowDownUpIcon, FolderPlusIcon, SearchIcon, SquarePenIcon, XIcon } from "lucide-react"; import { type ComponentProps, type KeyboardEvent as ReactKeyboardEvent, @@ -7,11 +10,31 @@ import { type RefObject, } from "react"; +import { updateClientSettings, useClientSettings } from "~/hooks/useSettings"; import { cn } from "~/lib/utils"; import { Button } from "../ui/button"; +import { + Menu, + MenuCheckboxItem, + MenuGroup, + MenuGroupLabel, + MenuPopup, + MenuRadioGroup, + MenuRadioItem, + MenuRadioItemIndicator, + MenuSeparator, + MenuTrigger, +} from "../ui/menu"; import { SidebarInput, SidebarMenuButton } from "../ui/sidebar"; import { Tooltip, TooltipPopup, TooltipTrigger } from "../ui/tooltip"; +export const SIDEBAR_PROJECT_SORT_ORDER_LABELS: Record = { + updated_at: msg("Last user message"), + created_at: msg("Created at"), + manual: msg("Manual"), +}; +export const isSidebarProjectSortOrder = Schema.is(SidebarProjectSortOrder); + export interface SidebarThreadHeaderProps { hasProjects: boolean; projectScope: ReactNode; @@ -100,6 +123,7 @@ export function SidebarThreadHeader({ {hasProjects ? ( <> {projectScope} + @@ -130,6 +154,76 @@ export function SidebarThreadHeader({ ); } +function SidebarViewMenu() { + const m = useMessages(); + const groupByProject = useClientSettings((settings) => settings.boringSidebarEnabled); + const projectSortOrder = useClientSettings((settings) => settings.sidebarProjectSortOrder); + const workingShelfEnabled = useClientSettings((settings) => settings.sidebarWorkingShelfEnabled); + const multilineRows = useClientSettings((settings) => settings.boringSidebarMultilineRows); + + return ( + + }> + + + {/* Refocusing the trigger after a pointer close shows its focus ring and opens its tooltip. */} + closeType === "keyboard"}> + + void updateClientSettings({ boringSidebarEnabled: checked }) + } + > + Group by project + + {groupByProject ? ( + + void updateClientSettings({ boringSidebarMultilineRows: checked }) + } + > + Two-line rows + + ) : ( + + void updateClientSettings({ sidebarWorkingShelfEnabled: checked }) + } + > + Working section + + )} + + + Sort projects + { + if (isSidebarProjectSortOrder(value)) { + void updateClientSettings({ sidebarProjectSortOrder: value }); + } + }} + > + {SidebarProjectSortOrder.literals.map((sortOrder) => ( + + + {m(SIDEBAR_PROJECT_SORT_ORDER_LABELS[sortOrder])} + + + + ))} + + + + + ); +} + export function SidebarHeaderIconButton({ label, tooltip = label, diff --git a/docs/user/thread-sidebar.md b/docs/user/thread-sidebar.md index e5fe950e140a2..6d62f163fd960 100644 --- a/docs/user/thread-sidebar.md +++ b/docs/user/thread-sidebar.md @@ -141,8 +141,9 @@ there. A thread returns to the top of the active list when it finishes, fails, o answer. The Working section lists the thread you last sent work to first. Pinned threads stay in the pinned section. -This is on by default. Turn it off with **Settings → General → Working section** on web and desktop, -or **Settings → General → Working section** on iOS and Android. Each device keeps its own +This is on by default. Turn it off from the sidebar's **View options** menu or +**Settings → General → Working section** on web and desktop, or **Settings → General → Working +section** on iOS and Android. Each device keeps its own choice. While this is on, the active list is ordered by when each thread last came back to you, so you @@ -150,8 +151,9 @@ cannot drag or move threads within it. Your saved order returns when you turn it ### Group threads by project (beta) -Turn on **Settings → Labs → Boring sidebar (group by project)** on web and desktop to put active -threads in a folder for each project. Pinned projects come first. A project with no active +Turn on **Group by project** in the sidebar's **View options** menu, or **Settings → Labs → Boring +sidebar (group by project)**, on web and desktop to put active threads in a folder for each +project. The same menu sets the project order. Pinned projects come first. A project with no active threads has no folder unless it is pinned or you pick it in the project filter. An archived project's folder comes last and shows only while it has active threads. Click a folder to open or close it. A closed folder shows how many threads it holds, and a dot when one of them