From ff05d5b2b53df93114313d7991c2b30393e99620 Mon Sep 17 00:00:00 2001 From: "yves.gugger" Date: Wed, 10 Dec 2025 16:30:38 +0100 Subject: [PATCH] feat: Add reusable filter/search components for consistency NEW COMPONENTS in PremiumTable.tsx: 1. SearchInput - Consistent search box styling across all pages - Left-aligned search icon - Clear button (X) when text entered - Props: value, onChange, placeholder, onClear 2. TabBar - Consistent tab styling with counts - Support for accent/warning/default colors - Optional icons - Wraps on mobile - Props: tabs[], activeTab, onChange 3. FilterBar - Simple flex container for filter rows - Responsive: stacks on mobile, row on desktop - Props: children 4. SelectDropdown - Consistent select styling - Custom chevron icon - Props: value, onChange, options[] 5. ActionButton - Consistent button styling - Variants: primary (accent), secondary (outlined), ghost - Sizes: default, small - Optional icon - Props: children, onClick, disabled, variant, size, icon These components ensure visual consistency across all Command Center pages for search, filtering, and actions. --- frontend/src/components/PremiumTable.tsx | 198 +++++++++++++++++++++++ 1 file changed, 198 insertions(+) diff --git a/frontend/src/components/PremiumTable.tsx b/frontend/src/components/PremiumTable.tsx index 42fac28..6aa5ac2 100755 --- a/frontend/src/components/PremiumTable.tsx +++ b/frontend/src/components/PremiumTable.tsx @@ -432,3 +432,201 @@ export function SectionHeader({ ) } +// ============================================================================ +// SEARCH INPUT (consistent search styling) +// ============================================================================ + +import { Search, X } from 'lucide-react' + +export function SearchInput({ + value, + onChange, + placeholder = 'Search...', + onClear, + className, +}: { + value: string + onChange: (value: string) => void + placeholder?: string + onClear?: () => void + className?: string +}) { + return ( +
+ + onChange(e.target.value)} + placeholder={placeholder} + className="w-full h-10 pl-10 pr-9 bg-background-secondary/50 border border-border/40 rounded-xl + text-sm text-foreground placeholder:text-foreground-subtle + focus:outline-none focus:border-accent/50 focus:bg-background-secondary/80 transition-all" + /> + {value && (onClear || onChange) && ( + + )} +
+ ) +} + +// ============================================================================ +// TAB BAR (consistent tab styling) +// ============================================================================ + +interface TabItem { + id: string + label: string + icon?: React.ComponentType<{ className?: string }> + count?: number + color?: 'default' | 'accent' | 'warning' +} + +export function TabBar({ + tabs, + activeTab, + onChange, + className, +}: { + tabs: TabItem[] + activeTab: string + onChange: (id: string) => void + className?: string +}) { + return ( +
+ {tabs.map((tab) => { + const isActive = activeTab === tab.id + const Icon = tab.icon + + return ( + + ) + })} +
+ ) +} + +// ============================================================================ +// FILTER BAR (row of filters: search + select + buttons) +// ============================================================================ + +export function FilterBar({ + children, + className, +}: { + children: ReactNode + className?: string +}) { + return ( +
+ {children} +
+ ) +} + +// ============================================================================ +// SELECT DROPDOWN (consistent select styling) +// ============================================================================ + +import { ChevronDown } from 'lucide-react' + +export function SelectDropdown({ + value, + onChange, + options, + className, +}: { + value: string + onChange: (value: string) => void + options: { value: string; label: string }[] + className?: string +}) { + return ( +
+ + +
+ ) +} + +// ============================================================================ +// ACTION BUTTON (consistent button styling) +// ============================================================================ + +export function ActionButton({ + children, + onClick, + disabled, + variant = 'primary', + size = 'default', + icon: Icon, + className, +}: { + children: ReactNode + onClick?: () => void + disabled?: boolean + variant?: 'primary' | 'secondary' | 'ghost' + size?: 'small' | 'default' + icon?: React.ComponentType<{ className?: string }> + className?: string +}) { + return ( + + ) +} +