fix accent color variables

This commit is contained in:
Cole Goldsmith 2025-09-30 11:22:37 -05:00
parent 90639d9010
commit 70a75ee63d
7 changed files with 154 additions and 232 deletions

View file

@ -1,78 +1,78 @@
"use client"; "use client";
import React, { type SVGProps } from "react"; import React, { type SVGProps } from "react";
import { Button } from "@/components/ui/button";
import { import {
Popover, Popover,
PopoverContent, PopoverContent,
PopoverTrigger, PopoverTrigger,
} from "@/components/ui/popover"; } from "@/components/ui/popover";
import { import {
Filter as FilterIcon, File,
Star,
Book, Book,
FileText, Scroll,
Folder, Library,
Globe, Map,
Calendar, FileImage,
User, Layers3,
Users,
Tag,
Briefcase,
Building2,
Cog,
Database, Database,
Cpu, Folder,
Bot, Archive,
MessageSquare, MessagesSquare,
Search, SquareStack,
Ghost,
Gem,
Swords,
Bolt,
Shield, Shield,
Lock, Hammer,
Key, Globe,
Link, HardDrive,
Mail, Upload,
Phone, Cable,
ShoppingCart,
ShoppingBag,
Check, Check,
Plus,
} from "lucide-react"; } from "lucide-react";
import { filterAccentClasses } from "./knowledge-filter-panel"; import { filterAccentClasses } from "./knowledge-filter-panel";
import { cn } from "@/lib/utils";
const ICON_MAP = { const ICON_MAP = {
Filter: FilterIcon, file: File,
Star, book: Book,
Book, scroll: Scroll,
FileText, library: Library,
Folder, map: Map,
Globe, image: FileImage,
Calendar, layers3: Layers3,
User, database: Database,
Users, folder: Folder,
Tag, archive: Archive,
Briefcase, messagesSquare: MessagesSquare,
Building2, squareStack: SquareStack,
Cog, ghost: Ghost,
Database, gem: Gem,
Cpu, swords: Swords,
Bot, bolt: Bolt,
MessageSquare, shield: Shield,
Search, hammer: Hammer,
Shield, globe: Globe,
Lock, hardDrive: HardDrive,
Key, upload: Upload,
Link, cable: Cable,
Mail, shoppingCart: ShoppingCart,
Phone, shoppingBag: ShoppingBag,
} as const; } as const;
export type IconKey = keyof typeof ICON_MAP; export type IconKey = keyof typeof ICON_MAP;
function iconKeyToComponent( export function iconKeyToComponent(
key: string key?: string
): React.ComponentType<SVGProps<SVGSVGElement>> { ): React.ComponentType<SVGProps<SVGSVGElement>> | undefined {
if (!key) return undefined;
return ( return (
(ICON_MAP as Record<string, React.ComponentType<SVGProps<SVGSVGElement>>>)[ ICON_MAP as Record<string, React.ComponentType<SVGProps<SVGSVGElement>>>
key )[key];
] || FilterIcon
);
} }
const COLORS = [ const COLORS = [
@ -87,21 +87,21 @@ const COLORS = [
export type FilterColor = (typeof COLORS)[number]; export type FilterColor = (typeof COLORS)[number];
const colorSwatchClasses = { const colorSwatchClasses = {
zinc: "bg-muted-foreground", zinc: "bg-muted-foreground text-accent-foreground",
pink: "bg-accent-pink-foreground", pink: "bg-accent-pink-foreground text-accent-pink",
purple: "bg-accent-purple-foreground", purple: "bg-accent-purple-foreground text-accent-purple",
indigo: "bg-accent-indigo-foreground", indigo: "bg-accent-indigo-foreground text-accent-indigo",
emerald: "bg-accent-emerald-foreground", emerald: "bg-accent-emerald-foreground text-accent-emerald",
amber: "bg-accent-amber-foreground", amber: "bg-accent-amber-foreground text-accent-amber",
red: "bg-accent-red-foreground", red: "bg-accent-red-foreground text-accent-red",
"": "bg-muted-foreground", "": "bg-muted-foreground text-accent-foreground",
}; };
export interface FilterIconPopoverProps { export interface FilterIconPopoverProps {
color: FilterColor; color: FilterColor;
iconKey: IconKey | string; iconKey?: IconKey | undefined;
onColorChange: (c: FilterColor) => void; onColorChange: (c: FilterColor) => void;
onIconChange: (k: IconKey) => void; onIconChange: (k: IconKey | undefined) => void;
triggerClassName?: string; triggerClassName?: string;
} }
@ -116,56 +116,55 @@ export function FilterIconPopover({
return ( return (
<Popover> <Popover>
<PopoverTrigger asChild> <PopoverTrigger asChild>
<Button <button
type="button" className={cn(
variant="outline" "h-10 w-10 min-w-10 min-h-10 rounded-sm flex items-center justify-center transition-colors",
size="icon" filterAccentClasses[color || ""],
className={"h-8 w-8 p-0 " + (triggerClassName || "")} triggerClassName
)}
> >
<span {Icon && <Icon className="h-5 w-5" />}
className={ {!Icon && <Plus className="h-5 w-5" />}
filterAccentClasses[color || ""] + </button>
" inline-flex items-center justify-center rounded h-6 w-6"
}
>
<Icon className="h-3.5 w-3.5" />
</span>
</Button>
</PopoverTrigger> </PopoverTrigger>
<PopoverContent className="w-80" align="start"> <PopoverContent className="w-80" align="start">
<div className="space-y-3"> <div className="space-y-4">
<div className="grid grid-cols-7 items-center gap-2"> <div className="grid grid-cols-7 items-center gap-2">
{COLORS.map((c) => ( {COLORS.map((c) => (
<button <button
key={c} key={c}
type="button" type="button"
onClick={() => onColorChange(c)} onClick={() => onColorChange(c)}
className={ 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 " +
colorSwatchClasses[c || ""] colorSwatchClasses[c || ""]
} )}
aria-label={c} aria-label={c}
> >
{c === color && <Check className="h-3.5 w-3.5" />} {c === color && <Check className="h-3.5 w-3.5" />}
</button> </button>
))} ))}
</div> </div>
<div className="text-xs font-medium text-muted-foreground mt-2">
Icon
</div>
<div className="grid grid-cols-6 gap-2"> <div className="grid grid-cols-6 gap-2">
{(Object.keys(ICON_MAP) as IconKey[]).map((k) => { {Object.keys(ICON_MAP).map((k: string) => {
const OptIcon = ICON_MAP[k]; const OptIcon = ICON_MAP[k as IconKey];
const active = iconKey === k; const active = iconKey === k;
return ( return (
<button <button
key={k} key={k}
type="button" type="button"
onClick={() => onIconChange(k)} onClick={() => {
if (active) {
onIconChange(undefined);
} else {
onIconChange(k as IconKey);
}
}}
className={ className={
"h-8 w-8 inline-flex items-center justify-center rounded border " + "h-8 w-8 inline-flex items-center hover:text-foreground justify-center rounded border " +
(active ? "border-foreground" : "border-border") (active
? "border-muted-foreground text-foreground"
: "border-0 text-muted-foreground")
} }
aria-label={k} aria-label={k}
> >

View file

@ -2,73 +2,19 @@
import { useState } from "react"; import { useState } from "react";
import { Button } from "@/components/ui/button"; import { Button } from "@/components/ui/button";
import { import { Loader2, Plus, X } from "lucide-react";
Filter as FilterIcon,
Loader2,
Plus,
X,
Star,
Book,
FileText,
Folder,
Globe,
Calendar,
User,
Users,
Tag,
Briefcase,
Building2,
Cog,
Database,
Cpu,
Bot,
MessageSquare,
Search,
Shield,
Lock,
Key,
Link,
Mail,
Phone,
} from "lucide-react";
import { cn } from "@/lib/utils"; import { cn } from "@/lib/utils";
import { import {
useGetFiltersSearchQuery, useGetFiltersSearchQuery,
type KnowledgeFilter, type KnowledgeFilter,
} from "@/src/app/api/queries/useGetFiltersSearchQuery"; } from "@/src/app/api/queries/useGetFiltersSearchQuery";
import { useKnowledgeFilter } from "@/src/contexts/knowledge-filter-context"; import { useKnowledgeFilter } from "@/src/contexts/knowledge-filter-context";
import type { SVGProps } from "react"; import {
FilterColor,
const ICON_MAP = { IconKey,
Filter: FilterIcon, iconKeyToComponent,
Star, } from "./filter-icon-popover";
Book, import { filterAccentClasses } from "./knowledge-filter-panel";
FileText,
Folder,
Globe,
Calendar,
User,
Users,
Tag,
Briefcase,
Building2,
Cog,
Database,
Cpu,
Bot,
MessageSquare,
Search,
Shield,
Lock,
Key,
Link,
Mail,
Phone,
} as const;
function iconKeyToComponent(key: string): React.ComponentType<SVGProps<SVGSVGElement>> {
return (ICON_MAP as Record<string, React.ComponentType<SVGProps<SVGSVGElement>>>)[key] || FilterIcon;
}
interface ParsedQueryData { interface ParsedQueryData {
query: string; query: string;
@ -79,8 +25,8 @@ interface ParsedQueryData {
}; };
limit: number; limit: number;
scoreThreshold: number; scoreThreshold: number;
color?: "zinc" | "pink" | "purple" | "indigo" | "emerald" | "amber" | "red"; color?: FilterColor;
icon?: string; icon?: IconKey;
} }
interface KnowledgeFilterListProps { interface KnowledgeFilterListProps {
@ -103,6 +49,10 @@ export function KnowledgeFilterList({
const filters = data || []; const filters = data || [];
const handleFilterSelect = (filter: KnowledgeFilter) => { const handleFilterSelect = (filter: KnowledgeFilter) => {
if (filter.id === selectedFilter?.id) {
onFilterSelect(null);
return;
}
onFilterSelect(filter); onFilterSelect(filter);
}; };
@ -150,35 +100,24 @@ export function KnowledgeFilterList({
className={cn( className={cn(
"flex items-center gap-3 px-3 py-2 w-full rounded-lg hover:bg-accent hover:text-accent-foreground cursor-pointer group transition-colors", "flex items-center gap-3 px-3 py-2 w-full rounded-lg hover:bg-accent hover:text-accent-foreground cursor-pointer group transition-colors",
selectedFilter?.id === filter.id && selectedFilter?.id === filter.id &&
"bg-accent text-accent-foreground" "active bg-accent text-accent-foreground"
)} )}
> >
<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);
const color = (parsed.color || "zinc") as const Icon = iconKeyToComponent(parsed.icon);
| "zinc"
| "pink"
| "purple"
| "indigo"
| "emerald"
| "amber"
| "red";
const Icon = iconKeyToComponent(parsed.icon || "Filter");
const colorMap = {
zinc: "bg-zinc-500/20 text-zinc-500",
pink: "bg-pink-500/20 text-pink-500",
purple: "bg-purple-500/20 text-purple-500",
indigo: "bg-indigo-500/20 text-indigo-500",
emerald: "bg-emerald-500/20 text-emerald-500",
amber: "bg-amber-500/20 text-amber-500",
red: "bg-red-500/20 text-red-500",
} as const;
const colorClasses = colorMap[color];
return ( return (
<div className={`flex items-center justify-center ${colorClasses} w-5 h-5 rounded`}> <div
<Icon className="h-3 w-3" /> className={cn(
"flex items-center justify-center w-5 h-5 rounded transition-colors",
filterAccentClasses[parsed.color || ""],
parsed.color === "zinc" &&
"group-hover:bg-background group-[.active]:bg-background"
)}
>
{Icon && <Icon className="h-3 w-3" />}
</div> </div>
); );
})()} })()}
@ -187,19 +126,19 @@ export function KnowledgeFilterList({
</div> </div>
</div> </div>
{filter.description && ( {filter.description && (
<div className="text-xs text-muted-foreground group-hover:text-accent-foreground/70 line-clamp-2"> <div className="text-xs text-muted-foreground line-clamp-2">
{filter.description} {filter.description}
</div> </div>
)} )}
<div className="flex items-center gap-2"> <div className="flex items-center gap-2">
<div className="text-xs text-muted-foreground group-hover:text-accent-foreground/70"> <div className="text-xs text-muted-foreground">
{new Date(filter.created_at).toLocaleDateString(undefined, { {new Date(filter.created_at).toLocaleDateString(undefined, {
month: "short", month: "short",
day: "numeric", day: "numeric",
year: "numeric", year: "numeric",
})} })}
</div> </div>
<span className="text-xs bg-muted text-muted-foreground px-1 py-0.5 rounded-sm"> <span className="text-xs bg-muted text-muted-foreground px-1 py-0.5 rounded-sm group-hover:bg-background group-[.active]:bg-background transition-colors">
{(() => { {(() => {
const dataSources = parseQueryData(filter.query_data) const dataSources = parseQueryData(filter.query_data)
.filters.data_sources; .filters.data_sources;
@ -210,19 +149,6 @@ export function KnowledgeFilterList({
</span> </span>
</div> </div>
</div> </div>
{selectedFilter?.id === filter.id && (
<Button
variant="ghost"
size="sm"
className="px-0"
onClick={(e) => {
e.stopPropagation();
onFilterSelect(null);
}}
>
<X className="h-4 w-4 flex-shrink-0 opacity-0 group-hover:opacity-100 text-muted-foreground" />
</Button>
)}
</div> </div>
)) ))
)} )}

View file

@ -14,7 +14,7 @@ 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 } from "@/components/filter-icon-popover"; import { FilterIconPopover, IconKey } from "@/components/filter-icon-popover";
interface FacetBucket { interface FacetBucket {
key: string; key: string;
@ -32,14 +32,14 @@ export const filterAccentClasses: Record<
"zinc" | "pink" | "purple" | "indigo" | "emerald" | "amber" | "red" | "", "zinc" | "pink" | "purple" | "indigo" | "emerald" | "amber" | "red" | "",
string string
> = { > = {
zinc: "bg-accent text-accent-foreground", 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-accent-foreground", "": "bg-accent text-muted-foreground",
}; };
export function KnowledgeFilterPanel() { export function KnowledgeFilterPanel() {
@ -62,7 +62,7 @@ export function KnowledgeFilterPanel() {
const [color, setColor] = useState< const [color, setColor] = useState<
"zinc" | "pink" | "purple" | "indigo" | "emerald" | "amber" | "red" "zinc" | "pink" | "purple" | "indigo" | "emerald" | "amber" | "red"
>("zinc"); >("zinc");
const [iconKey, setIconKey] = useState<string>("Filter"); 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("");
@ -108,7 +108,7 @@ export function KnowledgeFilterPanel() {
setName(selectedFilter.name); setName(selectedFilter.name);
setDescription(selectedFilter.description || ""); setDescription(selectedFilter.description || "");
setColor(parsedFilterData.color || "zinc"); setColor(parsedFilterData.color || "zinc");
setIconKey(parsedFilterData.icon || "Filter"); setIconKey(parsedFilterData.icon as IconKey);
} }
}, [selectedFilter, parsedFilterData]); }, [selectedFilter, parsedFilterData]);
@ -122,7 +122,7 @@ export function KnowledgeFilterPanel() {
setName(""); setName("");
setDescription(""); setDescription("");
setColor(parsedFilterData.color || "zinc"); setColor(parsedFilterData.color || "zinc");
setIconKey(parsedFilterData.icon || "Filter"); setIconKey(parsedFilterData.icon as IconKey);
} }
}, [createMode, parsedFilterData]); }, [createMode, parsedFilterData]);

View file

@ -179,7 +179,7 @@ export const useGetSearchQuery = (
const queryResult = useQuery( const queryResult = useQuery(
{ {
queryKey: ["search", effectiveQuery], queryKey: ["search", queryData],
placeholderData: (prev) => prev, placeholderData: (prev) => prev,
queryFn: getFiles, queryFn: getFiles,
...options, ...options,

View file

@ -32,18 +32,18 @@
--ring: 0 0% 0%; --ring: 0 0% 0%;
--placeholder-foreground: 240 5% 65%; --placeholder-foreground: 240 5% 65%;
--accent-amber: 48 96% 88%; /* amber-100 #fef3c7 */ --accent-amber: 48, 96%, 89%; /* amber-100 #fef3c7 */
--accent-amber-foreground: 26 90.5% 37.1%; /* amber-700 #b45309 */ --accent-amber-foreground: 26, 90%, 37%; /* amber-700 #b45309 */
--accent-emerald: 152 76% 90%; /* emerald-100 #d1fae5 */ --accent-emerald: 149, 80%, 90%; /* emerald-100 #d1fae5 */
--accent-emerald-foreground: 161.4 93.5% 30.4%; /* emerald-600 #059669 */ --accent-emerald-foreground: 161, 94%, 30%; /* emerald-600 #059669 */
--accent-red: 0 93.5% 94.1%; /* red-100 #fee2e2 */ --accent-red: 0, 93%, 94%; /* red-100 #fee2e2 */
--accent-red-foreground: 0 72.2% 50.6%; /* red-600 #dc2626 */ --accent-red-foreground: 0, 72%, 51%; /* red-600 #dc2626 */
--accent-indigo: 226.7 100% 94.9%; /* indigo-100 #e0e7ff */ --accent-indigo: 226, 100%, 94%; /* indigo-100 #e0e7ff */
--accent-indigo-foreground: 243.4 75.4% 58.6%; /* indigo-600 #4f46e5 */ --accent-indigo-foreground: 243, 75%, 59%; /* indigo-600 #4f46e5 */
--accent-pink: 320.6 76.1% 95.5%; /* pink-100 #fce7f3 */ --accent-pink: 326, 78%, 95%; /* pink-100 #fce7f3 */
--accent-pink-foreground: 333.3 71.4% 50.6%; /* pink-600 #db2777 */ --accent-pink-foreground: 333, 71%, 51%; /* pink-600 #db2777 */
--accent-purple: 270 100% 95.5%; /* purple-100 #f3e8ff */ --accent-purple: 269, 100%, 95%; /* purple-100 #f3e8ff */
--accent-purple-foreground: 262.1 83.3% 57.8%; /* purple-600 #7c3aed */ --accent-purple-foreground: 271, 81%, 56%; /* purple-600 #7c3aed */
/* Component Colors */ /* Component Colors */
--component-icon: #d8598a; --component-icon: #d8598a;
@ -80,21 +80,18 @@
--ring: 0 0% 100%; --ring: 0 0% 100%;
--placeholder-foreground: 240 4% 46%; --placeholder-foreground: 240 4% 46%;
--accent-amber: 23 88% 27%; /* amber-900 #78350f */ --accent-amber: 22, 78%, 26%; /* amber-900 #78350f */
--accent-amber-foreground: 45.9 96.7% 64.5%; /* amber-300 #fcd34d */ --accent-amber-foreground: 46, 97%, 65%; /* amber-300 #fcd34d */
--accent-emerald: 164.3 88% 16.1%; /* emerald-900 #064e3b */ --accent-emerald: 164, 86%, 16%; /* emerald-900 #064e3b */
--accent-emerald-foreground: 156.2 71.6% 66.9%; /* emerald-400 #34d399 */ --accent-emerald-foreground: 158, 64%, 52%; /* emerald-400 #34d399 */
--accent-red: 0 75% 30%; /* red-900 #7f1d1d */ --accent-red: 0, 63%, 31%; /* red-900 #7f1d1d */
--accent-red-foreground: 0 93.5% 81.8%; /* red-400 #f87171 */ --accent-red-foreground: 0, 91%, 71%; /* red-400 #f87171 */
--accent-indigo: 239 46% 34.5%; /* indigo-900 #312e81 */ --accent-indigo: 242, 47%, 34%; /* indigo-900 #312e81 */
--accent-indigo-foreground: 234.5 89.5% 74.9%; /* indigo-400 #818cf8 */ --accent-indigo-foreground: 234, 89%, 74%; /* indigo-400 #818cf8 */
--accent-pink: 327.4 70.7% 31.4%; /* pink-900 #831843 */ --accent-pink: 336, 69%, 30%; /* pink-900 #831843 */
--accent-pink-foreground: 328.6 85.5% 70.2%; /* pink-400 #f472b6 */ --accent-pink-foreground: 329, 86%, 70%; /* pink-400 #f472b6 */
--accent-purple: 261.2 67.8% 34.3%; /* purple-900 #4c1d95 */ --accent-purple: 274, 66%, 32%; /* purple-900 #4c1d95 */
--accent-purple-foreground: 255.1 89.5% 76.3%; /* purple-400 #a78bfa */ --accent-purple-foreground: 270, 95%, 75%; /* purple-400 #a78bfa */
--warning: 45.9 96.7% 64.5%;
--warning-foreground: 240 6% 10%;
} }
* { * {

View file

@ -147,7 +147,7 @@ function SearchPage() {
initialFlex: 0.5, initialFlex: 0.5,
cellRenderer: ({ value }: CustomCellRendererProps<File>) => { cellRenderer: ({ value }: CustomCellRendererProps<File>) => {
return ( return (
<span className="text-xs text-green-400 bg-green-400/20 px-2 py-1 rounded"> <span className="text-xs text-accent-emerald-foreground bg-accent-emerald px-2 py-1 rounded">
{value?.toFixed(2) ?? "-"} {value?.toFixed(2) ?? "-"}
</span> </span>
); );

View file

@ -16,27 +16,27 @@ interface StatusBadgeProps {
const statusConfig = { const statusConfig = {
processing: { processing: {
label: "Processing", label: "Processing",
className: "text-muted-foreground dark:text-muted-foreground ", className: "text-muted-foreground ",
}, },
active: { active: {
label: "Active", label: "Active",
className: "text-emerald-600 dark:text-emerald-400 ", className: "text-accent-emerald-foreground ",
}, },
unavailable: { unavailable: {
label: "Unavailable", label: "Unavailable",
className: "text-red-600 dark:text-red-400 ", className: "text-accent-red-foreground ",
}, },
failed: { failed: {
label: "Failed", label: "Failed",
className: "text-red-600 dark:text-red-400 ", className: "text-accent-red-foreground ",
}, },
hidden: { hidden: {
label: "Hidden", label: "Hidden",
className: "text-zinc-400 dark:text-zinc-500 ", className: "text-muted-foreground ",
}, },
sync: { sync: {
label: "Sync", label: "Sync",
className: "text-amber-700 dark:text-amber-300 underline", className: "text-accent-amber-foreground underline",
}, },
}; };