Skip to content

Commit 5380dc7

Browse files
committed
feat(layout): add vertical feature tabs sidebar
- Add featureTabsLayout atom with vertical as default - Create VerticalFeatureTabs component with resizable width - Support project drag-and-drop reordering in vertical mode - Add layout toggle in Settings dialog - Simplify profile menu (remove redundant nav items)
1 parent 2f74bee commit 5380dc7

7 files changed

Lines changed: 676 additions & 38 deletions

File tree

src/App.tsx

Lines changed: 37 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@ import {
66
HomeIcon, ReaderIcon, GearIcon, LayersIcon, CubeIcon, ChatBubbleIcon,
77
DoubleArrowLeftIcon,
88
} from "@radix-ui/react-icons";
9-
import { GlobalHeader } from "./components/GlobalHeader";
9+
import { GlobalHeader, VerticalFeatureTabs } from "./components/GlobalHeader";
1010
import { setAutoCopyOnSelect, getAutoCopyOnSelect } from "./components/Terminal";
1111
import { Collapsible, CollapsibleTrigger, CollapsibleContent as CollapsibleBody } from "./components/ui/collapsible";
1212
import { Switch } from "./components/ui/switch";
@@ -24,7 +24,7 @@ import type {
2424
TemplatesCatalog, UserProfile,
2525
} from "./types";
2626
import { useAtom } from "jotai";
27-
import { sidebarCollapsedAtom, marketplaceCategoryAtom, shortenPathsAtom, profileAtom, navigationStateAtom, viewAtom, viewHistoryAtom, historyIndexAtom } from "./store";
27+
import { sidebarCollapsedAtom, marketplaceCategoryAtom, shortenPathsAtom, profileAtom, navigationStateAtom, viewAtom, viewHistoryAtom, historyIndexAtom, featureTabsLayoutAtom, workspaceDataAtom } from "./store";
2828
import { AppConfigContext, useAppConfig, type AppConfig } from "./context";
2929
import { FEATURES, FEATURE_ICONS } from "./constants";
3030
// Modular views
@@ -103,6 +103,8 @@ function App() {
103103
}, [setNavigationState]);
104104

105105
const [sidebarCollapsed, setSidebarCollapsed] = useAtom(sidebarCollapsedAtom);
106+
const [featureTabsLayout] = useAtom(featureTabsLayoutAtom);
107+
const [workspace] = useAtom(workspaceDataAtom);
106108
const [marketplaceCategory, setMarketplaceCategory] = useAtom(marketplaceCategoryAtom);
107109
const [catalog, setCatalog] = useState<TemplatesCatalog | null>(null);
108110
const [homeDir, setHomeDir] = useState("");
@@ -246,6 +248,9 @@ function App() {
246248
onShowProfileDialog={() => setShowProfileDialog(true)}
247249
onShowSettings={() => setShowSettings(true)}
248250
/>
251+
<div className="flex-1 flex overflow-hidden">
252+
{/* Vertical Feature Tabs Sidebar */}
253+
{featureTabsLayout === "vertical" && workspace && <VerticalFeatureTabs />}
249254
<main className="flex-1 overflow-auto">
250255
{view.type === "home" && (
251256
<Home
@@ -432,6 +437,7 @@ function App() {
432437
)}
433438
{view.type === "feature-todo" && <FeatureTodo feature={view.feature} />}
434439
</main>
440+
</div>
435441
</div>
436442
) : (
437443
<div className="h-screen bg-canvas flex">
@@ -785,6 +791,7 @@ function App() {
785791
function AppSettingsDialog({ open, onClose }: { open: boolean; onClose: () => void }) {
786792
const { shortenPaths, setShortenPaths } = useAppConfig();
787793
const [autoCopy, setAutoCopy] = useState(getAutoCopyOnSelect);
794+
const [featureTabsLayout, setFeatureTabsLayout] = useAtom(featureTabsLayoutAtom);
788795

789796
const handleAutoCopyChange = (checked: boolean) => {
790797
setAutoCopy(checked);
@@ -812,6 +819,34 @@ function AppSettingsDialog({ open, onClose }: { open: boolean; onClose: () => vo
812819
</div>
813820
<Switch checked={shortenPaths} onCheckedChange={setShortenPaths} />
814821
</div>
822+
<div className="flex items-center justify-between">
823+
<div>
824+
<p className="text-sm font-medium text-ink">Project tabs layout</p>
825+
<p className="text-xs text-muted-foreground">Position of project/feature tabs</p>
826+
</div>
827+
<div className="flex gap-0.5 p-0.5 bg-muted rounded-lg">
828+
<button
829+
onClick={() => setFeatureTabsLayout("horizontal")}
830+
className={`px-2.5 py-1 text-xs rounded-md transition-colors ${
831+
featureTabsLayout === "horizontal"
832+
? "bg-background text-ink shadow-sm"
833+
: "text-muted-foreground hover:text-ink"
834+
}`}
835+
>
836+
Horizontal
837+
</button>
838+
<button
839+
onClick={() => setFeatureTabsLayout("vertical")}
840+
className={`px-2.5 py-1 text-xs rounded-md transition-colors ${
841+
featureTabsLayout === "vertical"
842+
? "bg-background text-ink shadow-sm"
843+
: "text-muted-foreground hover:text-ink"
844+
}`}
845+
>
846+
Vertical
847+
</button>
848+
</div>
849+
</div>
815850
</div>
816851
{/* Terminal */}
817852
<div className="space-y-3">

src/components/GlobalHeader/GlobalHeader.tsx

Lines changed: 5 additions & 34 deletions
Original file line numberDiff line numberDiff line change
@@ -3,12 +3,11 @@ import { useAtom } from "jotai";
33
import { motion, AnimatePresence } from "framer-motion";
44
import {
55
PersonIcon, ChevronLeftIcon, ChevronRightIcon,
6-
GearIcon, LayersIcon, CubeIcon,
7-
RocketIcon, CounterClockwiseClockIcon, BookmarkIcon,
6+
RocketIcon, CounterClockwiseClockIcon, BookmarkIcon, LayersIcon,
87
} from "@radix-ui/react-icons";
98
import { Avatar, AvatarImage, AvatarFallback } from "../ui/avatar";
109
import { Popover, PopoverTrigger, PopoverContent } from "../ui/popover";
11-
import { sidebarCollapsedAtom, profileAtom, workspaceDataAtom, primaryFeatureAtom } from "@/store";
10+
import { sidebarCollapsedAtom, profileAtom, workspaceDataAtom, primaryFeatureAtom, featureTabsLayoutAtom } from "@/store";
1211
import { GlobalFeatureTabs } from "./GlobalFeatureTabs";
1312
import type { View, FeatureType } from "@/types";
1413

@@ -39,9 +38,10 @@ export function GlobalHeader({
3938
const [profile] = useAtom(profileAtom);
4039
const [workspace] = useAtom(workspaceDataAtom);
4140
const [primaryFeature, setPrimaryFeature] = useAtom(primaryFeatureAtom);
41+
const [featureTabsLayout] = useAtom(featureTabsLayoutAtom);
4242

43-
// Always show feature tabs when workspace data is available
44-
const showFeatureTabs = !!workspace;
43+
// Show horizontal feature tabs when workspace data is available AND layout is horizontal
44+
const showFeatureTabs = !!workspace && featureTabsLayout === "horizontal";
4545

4646
// Main nav features - use primaryFeature for active state (not affected by profile menu clicks)
4747
const mainNavFeatures = ["workspace", "chat", "kb-distill", "kb-reference"] as const;
@@ -128,7 +128,6 @@ export function GlobalHeader({
128128
profile={profile}
129129
onShowProfileDialog={onShowProfileDialog}
130130
onShowSettings={onShowSettings}
131-
onFeatureClick={onFeatureClick}
132131
/>
133132
</div>
134133
);
@@ -176,12 +175,10 @@ function ProfileMenu({
176175
profile,
177176
onShowProfileDialog,
178177
onShowSettings,
179-
onFeatureClick,
180178
}: {
181179
profile: { nickname: string; avatarUrl: string };
182180
onShowProfileDialog: () => void;
183181
onShowSettings: () => void;
184-
onFeatureClick?: (feature: FeatureType) => void;
185182
}) {
186183
return (
187184
<div className="pr-4">
@@ -211,32 +208,6 @@ function ProfileMenu({
211208
>
212209
Settings
213210
</button>
214-
{onFeatureClick && (
215-
<>
216-
<div className="h-px bg-border my-1" />
217-
<button
218-
onClick={() => onFeatureClick("settings")}
219-
className="w-full text-left px-2 py-1.5 text-sm text-muted-foreground hover:text-ink hover:bg-card-alt rounded-md transition-colors flex items-center gap-2"
220-
>
221-
<GearIcon className="w-4 h-4" />
222-
Configuration
223-
</button>
224-
<button
225-
onClick={() => onFeatureClick("features")}
226-
className="w-full text-left px-2 py-1.5 text-sm text-muted-foreground hover:text-ink hover:bg-card-alt rounded-md transition-colors flex items-center gap-2"
227-
>
228-
<LayersIcon className="w-4 h-4" />
229-
Features
230-
</button>
231-
<button
232-
onClick={() => onFeatureClick("marketplace")}
233-
className="w-full text-left px-2 py-1.5 text-sm text-muted-foreground hover:text-ink hover:bg-card-alt rounded-md transition-colors flex items-center gap-2"
234-
>
235-
<CubeIcon className="w-4 h-4" />
236-
Marketplace
237-
</button>
238-
</>
239-
)}
240211
</div>
241212
</PopoverContent>
242213
</Popover>

0 commit comments

Comments
 (0)