mirror of
https://github.com/snapotter-hq/SnapOtter.git
synced 2026-08-03 07:46:42 +02:00
feat(web): modality tabs on fullscreen grid page
Add a row of filter tabs (All, Image, Video, Audio, Documents, Data & Files) at the top of the fullscreen tool grid. Selecting a tab filters the visible tools by modality, and the existing search still works within the filtered set. Also registers the Wand2 icon needed by the new video-effects category header.
This commit is contained in:
@@ -105,6 +105,7 @@ import {
|
||||
Volume2,
|
||||
VolumeX,
|
||||
Wand,
|
||||
Wand2,
|
||||
Waves,
|
||||
Wrench,
|
||||
Zap,
|
||||
@@ -216,6 +217,7 @@ export const ICON_MAP: Record<string, LucideIcon> = {
|
||||
Volume2,
|
||||
VolumeX,
|
||||
Wand,
|
||||
Wand2,
|
||||
Waves,
|
||||
Wrench,
|
||||
Repeat,
|
||||
|
||||
@@ -1,6 +1,18 @@
|
||||
import type { CategoryInfo, Tool } from "@snapotter/shared";
|
||||
import { ANALYTICS_EVENTS, CATEGORIES, TOOLS } from "@snapotter/shared";
|
||||
import { Eye, EyeOff, FileImage, LayoutGrid, List, Search } from "lucide-react";
|
||||
import {
|
||||
AudioLines,
|
||||
Eye,
|
||||
EyeOff,
|
||||
FileImage,
|
||||
FileText,
|
||||
Image,
|
||||
LayoutGrid,
|
||||
List,
|
||||
Search,
|
||||
Table,
|
||||
Video,
|
||||
} from "lucide-react";
|
||||
import { useEffect, useMemo, useState } from "react";
|
||||
import { Link, useNavigate } from "react-router-dom";
|
||||
import { OtterLogo } from "@/components/common/otter-logo";
|
||||
@@ -14,6 +26,15 @@ import { getCategoryName, getToolDescription, getToolName } from "@/lib/tool-i18
|
||||
import { cn } from "@/lib/utils";
|
||||
import { useFeaturesStore } from "@/stores/features-store";
|
||||
|
||||
const MODALITY_TABS = [
|
||||
{ id: "all", label: "All", icon: LayoutGrid },
|
||||
{ id: "image", label: "Image", icon: Image },
|
||||
{ id: "video", label: "Video", icon: Video },
|
||||
{ id: "audio", label: "Audio", icon: AudioLines },
|
||||
{ id: "document", label: "Documents", icon: FileText },
|
||||
{ id: "file", label: "Data & Files", icon: Table },
|
||||
] as const;
|
||||
|
||||
export function FullscreenGridPage() {
|
||||
const { t } = useTranslation();
|
||||
const isMobile = useMobile();
|
||||
@@ -22,6 +43,7 @@ export function FullscreenGridPage() {
|
||||
const navigate = useNavigate();
|
||||
const [disabledTools, setDisabledTools] = useState<string[]>([]);
|
||||
const [experimentalEnabled, setExperimentalEnabled] = useState(false);
|
||||
const [modalityTab, setModalityTab] = useState<string>("all");
|
||||
const fetchFeatures = useFeaturesStore((s) => s.fetch);
|
||||
|
||||
useEffect(() => {
|
||||
@@ -48,15 +70,19 @@ export function FullscreenGridPage() {
|
||||
}, [disabledTools, experimentalEnabled]);
|
||||
|
||||
const filteredTools = useMemo(() => {
|
||||
if (!search) return visibleTools;
|
||||
let tools = visibleTools;
|
||||
if (modalityTab !== "all") {
|
||||
tools = tools.filter((t) => t.modality === modalityTab);
|
||||
}
|
||||
if (!search) return tools;
|
||||
const q = search.toLowerCase();
|
||||
return visibleTools.filter(
|
||||
return tools.filter(
|
||||
(t) =>
|
||||
t.name.toLowerCase().includes(q) ||
|
||||
t.description.toLowerCase().includes(q) ||
|
||||
t.category.toLowerCase().includes(q),
|
||||
);
|
||||
}, [search, visibleTools]);
|
||||
}, [search, visibleTools, modalityTab]);
|
||||
|
||||
useEffect(() => {
|
||||
if (!search) return;
|
||||
@@ -136,6 +162,32 @@ export function FullscreenGridPage() {
|
||||
</div>
|
||||
</header>
|
||||
|
||||
{/* Modality tabs */}
|
||||
<div className="max-w-7xl mx-auto px-4 sm:px-6 pt-4">
|
||||
<div className="flex flex-wrap gap-2">
|
||||
{MODALITY_TABS.map((tab) => {
|
||||
const TabIcon = tab.icon;
|
||||
const isActive = modalityTab === tab.id;
|
||||
return (
|
||||
<button
|
||||
key={tab.id}
|
||||
type="button"
|
||||
onClick={() => setModalityTab(tab.id)}
|
||||
className={cn(
|
||||
"flex items-center gap-1.5 px-3 py-1.5 rounded-full text-sm font-medium transition-colors",
|
||||
isActive
|
||||
? "bg-primary text-primary-foreground"
|
||||
: "bg-muted text-muted-foreground hover:bg-muted/80 hover:text-foreground",
|
||||
)}
|
||||
>
|
||||
<TabIcon className="h-3.5 w-3.5" />
|
||||
{tab.label}
|
||||
</button>
|
||||
);
|
||||
})}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Grid */}
|
||||
<main className="max-w-7xl mx-auto px-4 sm:px-6 py-6">
|
||||
<h1 className="sr-only">{t.nav.tools}</h1>
|
||||
|
||||
Reference in New Issue
Block a user