make filter icon required

This commit is contained in:
Cole Goldsmith 2025-09-30 13:29:43 -05:00
parent ef98a5a826
commit 60cb732ce2
7 changed files with 48 additions and 61 deletions

View file

@ -7,7 +7,6 @@ import {
PopoverTrigger, PopoverTrigger,
} from "@/components/ui/popover"; } from "@/components/ui/popover";
import { import {
File,
Book, Book,
Scroll, Scroll,
Library, Library,
@ -32,13 +31,13 @@ import {
ShoppingCart, ShoppingCart,
ShoppingBag, ShoppingBag,
Check, Check,
Plus, Filter,
} from "lucide-react"; } from "lucide-react";
import { filterAccentClasses } from "./knowledge-filter-panel"; import { filterAccentClasses } from "./knowledge-filter-panel";
import { cn } from "@/lib/utils"; import { cn } from "@/lib/utils";
const ICON_MAP = { const ICON_MAP = {
file: File, filter: Filter,
book: Book, book: Book,
scroll: Scroll, scroll: Scroll,
library: Library, library: Library,
@ -87,21 +86,20 @@ const COLORS = [
export type FilterColor = (typeof COLORS)[number]; export type FilterColor = (typeof COLORS)[number];
const colorSwatchClasses = { const colorSwatchClasses = {
zinc: "bg-muted-foreground text-accent-foreground", zinc: "bg-muted-foreground",
pink: "bg-accent-pink-foreground text-accent-pink", pink: "bg-accent-pink-foreground",
purple: "bg-accent-purple-foreground text-accent-purple", purple: "bg-accent-purple-foreground",
indigo: "bg-accent-indigo-foreground text-accent-indigo", indigo: "bg-accent-indigo-foreground",
emerald: "bg-accent-emerald-foreground text-accent-emerald", emerald: "bg-accent-emerald-foreground",
amber: "bg-accent-amber-foreground text-accent-amber", amber: "bg-accent-amber-foreground",
red: "bg-accent-red-foreground text-accent-red", red: "bg-accent-red-foreground",
"": "bg-muted-foreground text-accent-foreground",
}; };
export interface FilterIconPopoverProps { export interface FilterIconPopoverProps {
color: FilterColor; color: FilterColor;
iconKey?: IconKey | undefined; iconKey: IconKey;
onColorChange: (c: FilterColor) => void; onColorChange: (c: FilterColor) => void;
onIconChange: (k: IconKey | undefined) => void; onIconChange: (k: IconKey) => void;
triggerClassName?: string; triggerClassName?: string;
} }
@ -118,13 +116,12 @@ export function FilterIconPopover({
<PopoverTrigger asChild> <PopoverTrigger asChild>
<button <button
className={cn( className={cn(
"h-10 w-10 min-w-10 min-h-10 rounded-sm flex items-center justify-center transition-colors", "h-10 w-10 min-w-10 min-h-10 rounded-md flex items-center justify-center transition-colors",
filterAccentClasses[color || ""], filterAccentClasses[color],
triggerClassName triggerClassName
)} )}
> >
{Icon && <Icon className="h-5 w-5" />} {Icon && <Icon className="h-5 w-5" />}
{!Icon && <Plus className="h-5 w-5" />}
</button> </button>
</PopoverTrigger> </PopoverTrigger>
<PopoverContent className="w-80" align="start"> <PopoverContent className="w-80" align="start">
@ -136,8 +133,8 @@ export function FilterIconPopover({
type="button" type="button"
onClick={() => onColorChange(c)} onClick={() => onColorChange(c)}
className={cn( className={cn(
"flex items-center justify-center h-6 w-6 rounded-sm transition-colors", "flex items-center justify-center h-6 w-6 rounded-sm transition-colors text-primary",
colorSwatchClasses[c || ""] colorSwatchClasses[c]
)} )}
aria-label={c} aria-label={c}
> >
@ -153,18 +150,10 @@ export function FilterIconPopover({
<button <button
key={k} key={k}
type="button" type="button"
onClick={() => { onClick={() => onIconChange(k as IconKey)}
if (active) {
onIconChange(undefined);
} else {
onIconChange(k as IconKey);
}
}}
className={ className={
"h-8 w-8 inline-flex items-center hover:text-foreground justify-center rounded border " + "h-8 w-8 inline-flex items-center hover:text-foreground justify-center rounded " +
(active (active ? "bg-muted text-primary" : "text-muted-foreground")
? "border-muted-foreground text-foreground"
: "border-0 text-muted-foreground")
} }
aria-label={k} aria-label={k}
> >

View file

@ -2,7 +2,7 @@
import { useState } from "react"; import { useState } from "react";
import { Button } from "@/components/ui/button"; import { Button } from "@/components/ui/button";
import { Loader2, Plus, X } from "lucide-react"; import { Loader2, Plus } from "lucide-react";
import { cn } from "@/lib/utils"; import { cn } from "@/lib/utils";
import { import {
useGetFiltersSearchQuery, useGetFiltersSearchQuery,
@ -25,8 +25,8 @@ interface ParsedQueryData {
}; };
limit: number; limit: number;
scoreThreshold: number; scoreThreshold: number;
color?: FilterColor; color: FilterColor;
icon?: IconKey; icon: IconKey;
} }
interface KnowledgeFilterListProps { interface KnowledgeFilterListProps {
@ -106,13 +106,13 @@ export function KnowledgeFilterList({
<div className="flex flex-col gap-1 flex-1 min-w-0"> <div className="flex flex-col gap-1 flex-1 min-w-0">
<div className="flex items-center gap-2"> <div className="flex items-center gap-2">
{(() => { {(() => {
const parsed = parseQueryData(filter.query_data); const parsed = parseQueryData(filter.query_data) as ParsedQueryData;
const Icon = iconKeyToComponent(parsed.icon); const Icon = iconKeyToComponent(parsed.icon);
return ( return (
<div <div
className={cn( className={cn(
"flex items-center justify-center w-5 h-5 rounded transition-colors", "flex items-center justify-center w-5 h-5 rounded transition-colors",
filterAccentClasses[parsed.color || ""], filterAccentClasses[parsed.color],
parsed.color === "zinc" && parsed.color === "zinc" &&
"group-hover:bg-background group-[.active]:bg-background" "group-hover:bg-background group-[.active]:bg-background"
)} )}

View file

@ -14,7 +14,11 @@ import { useDeleteFilter } from "@/app/api/mutations/useDeleteFilter";
import { useUpdateFilter } from "@/app/api/mutations/useUpdateFilter"; import { useUpdateFilter } from "@/app/api/mutations/useUpdateFilter";
import { useCreateFilter } from "@/app/api/mutations/useCreateFilter"; import { useCreateFilter } from "@/app/api/mutations/useCreateFilter";
import { useGetSearchAggregations } from "@/src/app/api/queries/useGetSearchAggregations"; import { useGetSearchAggregations } from "@/src/app/api/queries/useGetSearchAggregations";
import { FilterIconPopover, IconKey } from "@/components/filter-icon-popover"; import {
FilterColor,
FilterIconPopover,
IconKey,
} from "@/components/filter-icon-popover";
interface FacetBucket { interface FacetBucket {
key: string; key: string;
@ -28,18 +32,14 @@ interface AvailableFacets {
connector_types: FacetBucket[]; connector_types: FacetBucket[];
} }
export const filterAccentClasses: Record< export const filterAccentClasses: Record<FilterColor, string> = {
"zinc" | "pink" | "purple" | "indigo" | "emerald" | "amber" | "red" | "", zinc: "bg-muted text-muted-foreground",
string
> = {
zinc: "bg-accent text-muted-foreground",
pink: "bg-accent-pink text-accent-pink-foreground", pink: "bg-accent-pink text-accent-pink-foreground",
purple: "bg-accent-purple text-accent-purple-foreground", purple: "bg-accent-purple text-accent-purple-foreground",
indigo: "bg-accent-indigo text-accent-indigo-foreground", indigo: "bg-accent-indigo text-accent-indigo-foreground",
emerald: "bg-accent-emerald text-accent-emerald-foreground", emerald: "bg-accent-emerald text-accent-emerald-foreground",
amber: "bg-accent-amber text-accent-amber-foreground", amber: "bg-accent-amber text-accent-amber-foreground",
red: "bg-accent-red text-accent-red-foreground", red: "bg-accent-red text-accent-red-foreground",
"": "bg-accent text-muted-foreground",
}; };
export function KnowledgeFilterPanel() { export function KnowledgeFilterPanel() {
@ -59,10 +59,8 @@ export function KnowledgeFilterPanel() {
const [name, setName] = useState(""); const [name, setName] = useState("");
const [description, setDescription] = useState(""); const [description, setDescription] = useState("");
const [isSaving, setIsSaving] = useState(false); const [isSaving, setIsSaving] = useState(false);
const [color, setColor] = useState< const [color, setColor] = useState<FilterColor>("zinc");
"zinc" | "pink" | "purple" | "indigo" | "emerald" | "amber" | "red" const [iconKey, setIconKey] = useState<IconKey>("filter");
>("zinc");
const [iconKey, setIconKey] = useState<IconKey>();
// Filter configuration states (mirror search page exactly) // Filter configuration states (mirror search page exactly)
const [query, setQuery] = useState(""); const [query, setQuery] = useState("");
@ -107,8 +105,8 @@ export function KnowledgeFilterPanel() {
setScoreThreshold(parsedFilterData.scoreThreshold || 0); setScoreThreshold(parsedFilterData.scoreThreshold || 0);
setName(selectedFilter.name); setName(selectedFilter.name);
setDescription(selectedFilter.description || ""); setDescription(selectedFilter.description || "");
setColor(parsedFilterData.color || "zinc"); setColor(parsedFilterData.color);
setIconKey(parsedFilterData.icon as IconKey); setIconKey(parsedFilterData.icon);
} }
}, [selectedFilter, parsedFilterData]); }, [selectedFilter, parsedFilterData]);
@ -121,8 +119,8 @@ export function KnowledgeFilterPanel() {
setScoreThreshold(parsedFilterData.scoreThreshold || 0); setScoreThreshold(parsedFilterData.scoreThreshold || 0);
setName(""); setName("");
setDescription(""); setDescription("");
setColor(parsedFilterData.color || "zinc"); setColor(parsedFilterData.color);
setIconKey(parsedFilterData.icon as IconKey); setIconKey(parsedFilterData.icon);
} }
}, [createMode, parsedFilterData]); }, [createMode, parsedFilterData]);
@ -268,7 +266,7 @@ export function KnowledgeFilterPanel() {
color={color} color={color}
iconKey={iconKey} iconKey={iconKey}
onColorChange={setColor} onColorChange={setColor}
onIconChange={(k) => setIconKey(k)} onIconChange={setIconKey}
/> />
<Input <Input
id="filter-name" id="filter-name"

View file

@ -2116,7 +2116,7 @@ function ChatPage() {
<div className="flex items-center gap-2 px-4 pt-3 pb-1"> <div className="flex items-center gap-2 px-4 pt-3 pb-1">
<span <span
className={`inline-flex items-center gap-1 px-2 py-1 rounded-full text-xs font-medium transition-colors ${ className={`inline-flex items-center gap-1 px-2 py-1 rounded-full text-xs font-medium transition-colors ${
filterAccentClasses[parsedFilterData?.color || ""] filterAccentClasses[parsedFilterData?.color || "zinc"]
}`} }`}
> >
@filter:{selectedFilter.name} @filter:{selectedFilter.name}

View file

@ -27,6 +27,8 @@
--accent-foreground: 0 0% 0%; --accent-foreground: 0 0% 0%;
--destructive: 0 72% 51%; --destructive: 0 72% 51%;
--destructive-foreground: 0 0% 100%; --destructive-foreground: 0 0% 100%;
--warning: 48, 96%, 89%;
--warning-foreground: 26, 90%, 37%;
--border: 240 4.8% 95.9%; --border: 240 4.8% 95.9%;
--input: 240 6% 90%; --input: 240 6% 90%;
--ring: 0 0% 0%; --ring: 0 0% 0%;
@ -49,10 +51,6 @@
--component-icon: #d8598a; --component-icon: #d8598a;
--flow-icon: #2f67d0; --flow-icon: #2f67d0;
/* Warning */
--warning: 48 96.6% 76.7%;
--warning-foreground: 240 6% 10%;
--radius: 0.5rem; --radius: 0.5rem;
} }
@ -75,6 +73,8 @@
--accent-foreground: 0 0% 100%; --accent-foreground: 0 0% 100%;
--destructive: 0 84% 60%; --destructive: 0 84% 60%;
--destructive-foreground: 0 0% 100%; --destructive-foreground: 0 0% 100%;
--warning: 22, 78%, 26%;
--warning-foreground: 46, 97%, 65%;
--border: 240 3.7% 15.9%; --border: 240 3.7% 15.9%;
--input: 240 5% 34%; --input: 240 5% 34%;
--ring: 0 0% 100%; --ring: 0 0% 100%;

View file

@ -256,7 +256,7 @@ function SearchPage() {
{selectedFilter?.name && ( {selectedFilter?.name && (
<div <div
className={`flex items-center gap-1 h-full px-1.5 py-0.5 rounded max-w-[300px] ${ className={`flex items-center gap-1 h-full px-1.5 py-0.5 rounded max-w-[300px] ${
filterAccentClasses[parsedFilterData?.color || ""] filterAccentClasses[parsedFilterData?.color || "zinc"]
}`} }`}
> >
<span className="truncate">{selectedFilter?.name}</span> <span className="truncate">{selectedFilter?.name}</span>

View file

@ -1,5 +1,6 @@
"use client"; "use client";
import { FilterColor, IconKey } from "@/components/filter-icon-popover";
import React, { import React, {
createContext, createContext,
type ReactNode, type ReactNode,
@ -27,9 +28,8 @@ export interface ParsedQueryData {
}; };
limit: number; limit: number;
scoreThreshold: number; scoreThreshold: number;
// Optional visual metadata for UI color: FilterColor;
color?: "zinc" | "pink" | "purple" | "indigo" | "emerald" | "amber" | "red"; icon: IconKey;
icon?: string; // lucide icon key we store in the UI mapping
} }
interface KnowledgeFilterContextType { interface KnowledgeFilterContextType {
@ -54,7 +54,7 @@ export function useKnowledgeFilter() {
const context = useContext(KnowledgeFilterContext); const context = useContext(KnowledgeFilterContext);
if (context === undefined) { if (context === undefined) {
throw new Error( throw new Error(
"useKnowledgeFilter must be used within a KnowledgeFilterProvider", "useKnowledgeFilter must be used within a KnowledgeFilterProvider"
); );
} }
return context; return context;
@ -127,7 +127,7 @@ export function KnowledgeFilterProvider({
limit: 10, limit: 10,
scoreThreshold: 0, scoreThreshold: 0,
color: "zinc", color: "zinc",
icon: "Filter", icon: "filter",
}); });
setIsPanelOpen(true); setIsPanelOpen(true);
}; };