"use client"; import { IconX, IconDeviceFloppy, IconBookmark } from "@tabler/icons-react"; import type { Icon } from "@tabler/icons-react"; import { cn } from "@/lib/utils"; import { MR_PRESETS, ISSUE_PRESETS, type PresetOption, type PresetGroup } from "./presets"; import type { SavedPreset } from "./use-saved-presets"; import { useTranslation } from "react-i18next"; import { useResponsiveBreakpoint } from "@/hooks/use-responsive-breakpoint"; import { SavedTaskViewDeleteConfirmation } from "@/components/confirmation/saved-task-view-delete-confirmation"; import { useSavedTaskViewDeleteConfirmation } from "@/components/confirmation/use-saved-task-view-delete-confirmation"; export type SidebarSelection = { kind: "mr" | "issue"; source: "preset" | "saved"; id: string; }; type PresetsSidebarProps = { selected: SidebarSelection; onSelect: (s: SidebarSelection) => void; savedPresets: SavedPreset[]; onDeleteSaved: (id: string) => void; canSaveCurrent: boolean; onSaveCurrent: () => void; mrPresets?: PresetOption[]; issuePresets?: PresetOption[]; }; function KindToggle({ kind, onChange, }: { kind: "mr" | "issue"; onChange: (k: "mr" | "issue") => void; }) { const { t } = useTranslation(); return (
{(["mr", "issue"] as const).map((value) => ( ))}
); } /** * `id` is a stable section identifier and `title` is the visible label. They * were one value: the testid was built as `gitlab-section-${title.toLowerCase()}`, * so translating the title moved the selector with it — under pseudo the inbox * section became `gitlab-section-ĩńƀōx`. apps/web/AGENTS.md is explicit that a * `data-testid` is never translated; deriving one from copy translates it by * the back door. */ function SectionHeader({ id, title }: { id: string; title: string }) { return (
{title}
); } function PresetItem({ label, Icon: ItemIcon, active, onClick, trailing, }: { label: string; Icon: Icon; active: boolean; onClick: () => void; trailing?: React.ReactNode; }) { const onKeyDown = (e: React.KeyboardEvent) => { // Ignore keys originating from descendants (e.g. the nested delete button), // otherwise Enter/Space on delete would also fire row selection. if (e.currentTarget !== e.target) return; if (e.key === "Enter" || e.key === " ") { e.preventDefault(); onClick(); } }; return (
{label} {trailing}
); } function PresetGroupList({ presets, group, selected, onSelect, kind, }: { presets: PresetOption[]; group: PresetGroup; selected: SidebarSelection; onSelect: (s: SidebarSelection) => void; kind: "mr" | "issue"; }) { const { t } = useTranslation(); const items = presets.filter((p) => p.group === group); if (items.length === 0) return null; return ( <> {items.map((p) => ( onSelect({ kind, source: "preset", id: p.value })} /> ))} ); } function SavedSection({ saved, selected, onSelect, onDelete, kind, canSaveCurrent, onSaveCurrent, }: { saved: SavedPreset[]; selected: SidebarSelection; onSelect: (s: SidebarSelection) => void; onDelete: (id: string) => void; kind: "mr" | "issue"; canSaveCurrent: boolean; onSaveCurrent: () => void; }) { const { t } = useTranslation(); const deletion = useSavedTaskViewDeleteConfirmation(saved); const { isMobile } = useResponsiveBreakpoint(); return ( <> {saved.length === 0 && (
{t("gitlab:noSavedQueriesYet")}
)} {saved.map((s) => { if (!isMobile && deletion.target?.id === s.id) { return (
{ if (!open) deletion.close(); }} onConfirm={onDelete} />
); } const deleteLabel = t("integrations:deleteSavedQueryNamed", { label: s.label }); return ( onSelect({ kind, source: "saved", id: s.id })} trailing={ } /> ); })} {isMobile && deletion.target && ( { if (!open) deletion.close(); }} onConfirm={onDelete} /> )} ); } function SaveQueryButton({ enabled, onClick }: { enabled: boolean; onClick: () => void }) { const { t } = useTranslation(); return ( ); } export function PresetsSidebar({ selected, onSelect, savedPresets, onDeleteSaved, canSaveCurrent, onSaveCurrent, mrPresets = MR_PRESETS, issuePresets = ISSUE_PRESETS, }: PresetsSidebarProps) { const presets = selected.kind === "mr" ? mrPresets : issuePresets; const saved = savedPresets.filter((p) => p.kind === selected.kind); const onKindChange = (kind: "mr" | "issue") => { const fallback = (kind === "mr" ? mrPresets : issuePresets)[0]?.value ?? ""; onSelect({ kind, source: "preset", id: fallback }); }; return ( ); }