mirror of
https://github.com/Termix-SSH/Termix.git
synced 2026-10-14 07:39:13 +00:00
139 lines
4.6 KiB
TypeScript
139 lines
4.6 KiB
TypeScript
import { useMemo } from "react";
|
|
import { useTranslation } from "react-i18next";
|
|
import { Columns2, LayoutPanelLeft, Rows2 } from "lucide-react";
|
|
import { Button } from "@/components/button";
|
|
import {
|
|
DropdownMenu,
|
|
DropdownMenuContent,
|
|
DropdownMenuItem,
|
|
DropdownMenuLabel,
|
|
DropdownMenuSeparator,
|
|
DropdownMenuShortcut,
|
|
DropdownMenuTrigger,
|
|
} from "@/components/dropdown-menu";
|
|
import type { LayoutNode } from "@/types/ui-types";
|
|
import {
|
|
buildPreset,
|
|
SPLIT_PRESETS,
|
|
type SplitPresetId,
|
|
} from "./split-presets";
|
|
import { MAX_PANES, type PaneEdge } from "./split-tree";
|
|
|
|
function NodeThumb({ node }: { node: LayoutNode }) {
|
|
if (node.kind === "pane") {
|
|
return <div className="size-full border border-current" />;
|
|
}
|
|
return (
|
|
<div
|
|
className={`flex size-full gap-0.5 ${node.direction === "row" ? "flex-row" : "flex-col"}`}
|
|
>
|
|
{node.children.map((child, index) => (
|
|
<div
|
|
key={child.id}
|
|
className="min-w-0 min-h-0"
|
|
style={{ flex: `${node.sizes[index]} 1 0px` }}
|
|
>
|
|
<NodeThumb node={child} />
|
|
</div>
|
|
))}
|
|
</div>
|
|
);
|
|
}
|
|
|
|
/** A small drawing of a preset's panes. */
|
|
export function PresetThumb({ id }: { id: SplitPresetId }) {
|
|
const node = useMemo(() => buildPreset(id), [id]);
|
|
return <NodeThumb node={node} />;
|
|
}
|
|
|
|
/**
|
|
* The tab bar's split button: split the active tab right or down, or lay it
|
|
* out as one of the presets.
|
|
*/
|
|
export function SplitLayoutMenu({
|
|
activeIsSplit,
|
|
canAddPane,
|
|
onSplit,
|
|
onPreset,
|
|
}: {
|
|
activeIsSplit: boolean;
|
|
canAddPane: boolean;
|
|
onSplit: (edge: Extract<PaneEdge, "right" | "bottom">) => void;
|
|
onPreset: (id: SplitPresetId) => void;
|
|
}) {
|
|
const { t } = useTranslation();
|
|
const fullTitle = canAddPane
|
|
? undefined
|
|
: t("splitScreen.maxPanes", { count: MAX_PANES });
|
|
return (
|
|
<DropdownMenu>
|
|
<DropdownMenuTrigger asChild>
|
|
<Button
|
|
variant="ghost"
|
|
size="icon"
|
|
className={`h-full w-12.5 rounded-none border-y-0 border-border ${activeIsSplit ? "text-accent-brand" : "text-muted-foreground hover:text-foreground"}`}
|
|
title={t("splitScreen.title")}
|
|
aria-label={t("splitScreen.title")}
|
|
>
|
|
<LayoutPanelLeft className="size-4" />
|
|
</Button>
|
|
</DropdownMenuTrigger>
|
|
<DropdownMenuContent align="end" sideOffset={1} className="w-64 p-1">
|
|
<DropdownMenuItem
|
|
disabled={!canAddPane}
|
|
title={fullTitle}
|
|
onSelect={() => onSplit("right")}
|
|
>
|
|
<Columns2 className="size-3.5" />
|
|
{t("splitScreen.splitRight")}
|
|
<DropdownMenuShortcut>Ctrl+Shift+\</DropdownMenuShortcut>
|
|
</DropdownMenuItem>
|
|
<DropdownMenuItem
|
|
disabled={!canAddPane}
|
|
title={fullTitle}
|
|
onSelect={() => onSplit("bottom")}
|
|
>
|
|
<Rows2 className="size-3.5" />
|
|
{t("splitScreen.splitDown")}
|
|
<DropdownMenuShortcut>Ctrl+Shift+-</DropdownMenuShortcut>
|
|
</DropdownMenuItem>
|
|
<DropdownMenuSeparator />
|
|
<DropdownMenuLabel className="text-[10px] font-semibold uppercase tracking-wider text-muted-foreground">
|
|
{activeIsSplit
|
|
? t("splitScreen.applyLayout")
|
|
: t("splitScreen.layouts")}
|
|
</DropdownMenuLabel>
|
|
<div className="grid grid-cols-4 gap-1 p-1">
|
|
{SPLIT_PRESETS.map((preset) => (
|
|
<DropdownMenuItem
|
|
key={preset.id}
|
|
className="flex h-12 items-center justify-center p-1.5 text-muted-foreground focus:text-accent-brand"
|
|
title={t(preset.titleKey)}
|
|
aria-label={t(preset.titleKey)}
|
|
onSelect={() => onPreset(preset.id)}
|
|
>
|
|
<div className="h-8 w-11">
|
|
<PresetThumb id={preset.id} />
|
|
</div>
|
|
</DropdownMenuItem>
|
|
))}
|
|
</div>
|
|
<DropdownMenuSeparator />
|
|
<DropdownMenuLabel className="text-[10px] font-semibold uppercase tracking-wider text-muted-foreground">
|
|
{t("splitScreen.shortcuts")}
|
|
</DropdownMenuLabel>
|
|
<div className="flex flex-col gap-1 px-2 pb-1.5 text-xs text-muted-foreground">
|
|
<div className="flex items-center justify-between gap-2">
|
|
<span>{t("splitScreen.shortcutNavigate")}</span>
|
|
<kbd className="font-mono text-[10px]">Alt+Arrows</kbd>
|
|
</div>
|
|
<div className="flex items-center justify-between gap-2">
|
|
<span>{t("splitScreen.zoom")}</span>
|
|
<kbd className="font-mono text-[10px]">Ctrl+Shift+Enter</kbd>
|
|
</div>
|
|
</div>
|
|
</DropdownMenuContent>
|
|
</DropdownMenu>
|
|
);
|
|
}
|