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.
633 lines
21 KiB
TypeScript
Executable File
633 lines
21 KiB
TypeScript
Executable File
'use client'
|
|
|
|
import { ReactNode } from 'react'
|
|
import clsx from 'clsx'
|
|
import { ChevronUp, ChevronDown, ChevronsUpDown, Loader2 } from 'lucide-react'
|
|
|
|
// ============================================================================
|
|
// PREMIUM TABLE - Elegant, consistent styling for all tables
|
|
// ============================================================================
|
|
|
|
interface Column<T> {
|
|
key: string
|
|
header: string | ReactNode
|
|
render?: (item: T, index: number) => ReactNode
|
|
className?: string
|
|
headerClassName?: string
|
|
hideOnMobile?: boolean
|
|
hideOnTablet?: boolean
|
|
sortable?: boolean
|
|
align?: 'left' | 'center' | 'right'
|
|
width?: string
|
|
}
|
|
|
|
interface PremiumTableProps<T> {
|
|
data: T[]
|
|
columns: Column<T>[]
|
|
keyExtractor: (item: T) => string | number
|
|
onRowClick?: (item: T) => void
|
|
emptyState?: ReactNode
|
|
emptyIcon?: ReactNode
|
|
emptyTitle?: string
|
|
emptyDescription?: string
|
|
loading?: boolean
|
|
sortBy?: string
|
|
sortDirection?: 'asc' | 'desc'
|
|
onSort?: (key: string) => void
|
|
compact?: boolean
|
|
striped?: boolean
|
|
hoverable?: boolean
|
|
}
|
|
|
|
export function PremiumTable<T>({
|
|
data,
|
|
columns,
|
|
keyExtractor,
|
|
onRowClick,
|
|
emptyState,
|
|
emptyIcon,
|
|
emptyTitle = 'No data',
|
|
emptyDescription,
|
|
loading,
|
|
sortBy,
|
|
sortDirection = 'asc',
|
|
onSort,
|
|
compact = false,
|
|
striped = false,
|
|
hoverable = true,
|
|
}: PremiumTableProps<T>) {
|
|
const cellPadding = compact ? 'px-4 py-3' : 'px-6 py-4'
|
|
const headerPadding = compact ? 'px-4 py-3' : 'px-6 py-4'
|
|
|
|
if (loading) {
|
|
return (
|
|
<div className="relative overflow-hidden rounded-2xl border border-border/40 bg-gradient-to-b from-background-secondary/40 to-background-secondary/20 backdrop-blur-sm">
|
|
<div className="divide-y divide-border/20">
|
|
{[...Array(5)].map((_, i) => (
|
|
<div key={i} className={clsx("flex gap-4 items-center", cellPadding)} style={{ animationDelay: `${i * 50}ms` }}>
|
|
<div className="h-5 w-32 bg-foreground/5 rounded-lg animate-pulse" />
|
|
<div className="h-5 w-24 bg-foreground/5 rounded-lg animate-pulse hidden sm:block" />
|
|
<div className="h-5 w-20 bg-foreground/5 rounded-lg animate-pulse ml-auto" />
|
|
</div>
|
|
))}
|
|
</div>
|
|
</div>
|
|
)
|
|
}
|
|
|
|
if (data.length === 0) {
|
|
return (
|
|
<div className="relative overflow-hidden rounded-2xl border border-border/40 bg-gradient-to-b from-background-secondary/40 to-background-secondary/20 backdrop-blur-sm">
|
|
<div className="px-8 py-16 text-center">
|
|
{emptyState || (
|
|
<>
|
|
{emptyIcon && <div className="flex justify-center mb-4">{emptyIcon}</div>}
|
|
<p className="text-foreground-muted font-medium">{emptyTitle}</p>
|
|
{emptyDescription && <p className="text-sm text-foreground-subtle mt-1">{emptyDescription}</p>}
|
|
</>
|
|
)}
|
|
</div>
|
|
</div>
|
|
)
|
|
}
|
|
|
|
return (
|
|
<div className="relative overflow-hidden rounded-2xl border border-border/40 bg-gradient-to-b from-background-secondary/40 to-background-secondary/20 backdrop-blur-sm shadow-[0_4px_24px_-4px_rgba(0,0,0,0.08)]">
|
|
<div className="overflow-x-auto">
|
|
<table className="w-full table-fixed">
|
|
<thead>
|
|
<tr className="border-b border-border/40 bg-background-secondary/30">
|
|
{columns.map((col) => (
|
|
<th
|
|
key={col.key}
|
|
className={clsx(
|
|
headerPadding,
|
|
"text-[11px] font-semibold text-foreground-subtle/70 uppercase tracking-wider whitespace-nowrap",
|
|
col.hideOnMobile && "hidden md:table-cell",
|
|
col.hideOnTablet && "hidden lg:table-cell",
|
|
col.align === 'right' && "text-right",
|
|
col.align === 'center' && "text-center",
|
|
!col.align && "text-left",
|
|
col.headerClassName
|
|
)}
|
|
style={col.width ? { width: col.width, minWidth: col.width } : undefined}
|
|
>
|
|
{col.sortable && onSort ? (
|
|
<button
|
|
onClick={() => onSort(col.key)}
|
|
className={clsx(
|
|
"inline-flex items-center gap-1.5 hover:text-foreground transition-colors group",
|
|
col.align === 'right' && "justify-end w-full",
|
|
col.align === 'center' && "justify-center w-full"
|
|
)}
|
|
>
|
|
{col.header}
|
|
<SortIndicator
|
|
active={sortBy === col.key}
|
|
direction={sortBy === col.key ? sortDirection : undefined}
|
|
/>
|
|
</button>
|
|
) : (
|
|
col.header
|
|
)}
|
|
</th>
|
|
))}
|
|
</tr>
|
|
</thead>
|
|
<tbody className="divide-y divide-border/20">
|
|
{data.map((item, index) => {
|
|
const key = keyExtractor(item)
|
|
|
|
return (
|
|
<tr
|
|
key={key}
|
|
onClick={() => onRowClick?.(item)}
|
|
className={clsx(
|
|
"group transition-all duration-200",
|
|
onRowClick && "cursor-pointer",
|
|
hoverable && "hover:bg-foreground/[0.02]",
|
|
striped && index % 2 === 1 && "bg-foreground/[0.01]"
|
|
)}
|
|
>
|
|
{columns.map((col) => (
|
|
<td
|
|
key={col.key}
|
|
className={clsx(
|
|
cellPadding,
|
|
"text-sm align-middle",
|
|
col.hideOnMobile && "hidden md:table-cell",
|
|
col.hideOnTablet && "hidden lg:table-cell",
|
|
col.align === 'right' && "text-right",
|
|
col.align === 'center' && "text-center",
|
|
!col.align && "text-left",
|
|
col.className
|
|
)}
|
|
>
|
|
{col.render
|
|
? col.render(item, index)
|
|
: (item as Record<string, unknown>)[col.key] as ReactNode
|
|
}
|
|
</td>
|
|
))}
|
|
</tr>
|
|
)
|
|
})}
|
|
</tbody>
|
|
</table>
|
|
</div>
|
|
</div>
|
|
)
|
|
}
|
|
|
|
// ============================================================================
|
|
// SORT INDICATOR
|
|
// ============================================================================
|
|
|
|
function SortIndicator({ active, direction }: { active: boolean; direction?: 'asc' | 'desc' }) {
|
|
if (!active) {
|
|
return <ChevronsUpDown className="w-3.5 h-3.5 text-foreground-subtle/50 group-hover:text-foreground-muted transition-colors" />
|
|
}
|
|
return direction === 'asc'
|
|
? <ChevronUp className="w-3.5 h-3.5 text-accent" />
|
|
: <ChevronDown className="w-3.5 h-3.5 text-accent" />
|
|
}
|
|
|
|
// ============================================================================
|
|
// STATUS BADGE
|
|
// ============================================================================
|
|
|
|
type BadgeVariant = 'default' | 'success' | 'warning' | 'error' | 'accent' | 'info'
|
|
|
|
export function Badge({
|
|
children,
|
|
variant = 'default',
|
|
size = 'sm',
|
|
dot = false,
|
|
pulse = false,
|
|
}: {
|
|
children: ReactNode
|
|
variant?: BadgeVariant
|
|
size?: 'xs' | 'sm' | 'md'
|
|
dot?: boolean
|
|
pulse?: boolean
|
|
}) {
|
|
const variants: Record<BadgeVariant, string> = {
|
|
default: "bg-foreground/5 text-foreground-muted border-border/50",
|
|
success: "bg-accent/10 text-accent border-accent/20",
|
|
warning: "bg-amber-500/10 text-amber-400 border-amber-500/20",
|
|
error: "bg-red-500/10 text-red-400 border-red-500/20",
|
|
accent: "bg-accent/10 text-accent border-accent/20",
|
|
info: "bg-blue-500/10 text-blue-400 border-blue-500/20",
|
|
}
|
|
|
|
const sizes = {
|
|
xs: "text-[10px] px-1.5 py-0.5",
|
|
sm: "text-xs px-2 py-0.5",
|
|
md: "text-xs px-2.5 py-1",
|
|
}
|
|
|
|
return (
|
|
<span className={clsx(
|
|
"inline-flex items-center gap-1.5 font-medium rounded-md border",
|
|
variants[variant],
|
|
sizes[size]
|
|
)}>
|
|
{dot && (
|
|
<span className="relative flex h-2 w-2">
|
|
{pulse && (
|
|
<span className={clsx(
|
|
"animate-ping absolute inline-flex h-full w-full rounded-full opacity-75",
|
|
variant === 'success' || variant === 'accent' ? "bg-accent" :
|
|
variant === 'warning' ? "bg-amber-400" :
|
|
variant === 'error' ? "bg-red-400" : "bg-foreground"
|
|
)} />
|
|
)}
|
|
<span className={clsx(
|
|
"relative inline-flex rounded-full h-2 w-2",
|
|
variant === 'success' || variant === 'accent' ? "bg-accent" :
|
|
variant === 'warning' ? "bg-amber-400" :
|
|
variant === 'error' ? "bg-red-400" :
|
|
variant === 'info' ? "bg-blue-400" : "bg-foreground-muted"
|
|
)} />
|
|
</span>
|
|
)}
|
|
{children}
|
|
</span>
|
|
)
|
|
}
|
|
|
|
// ============================================================================
|
|
// TABLE ACTION BUTTON
|
|
// ============================================================================
|
|
|
|
export function TableActionButton({
|
|
icon: Icon,
|
|
onClick,
|
|
variant = 'default',
|
|
title,
|
|
disabled,
|
|
loading,
|
|
}: {
|
|
icon: React.ComponentType<{ className?: string }>
|
|
onClick?: () => void
|
|
variant?: 'default' | 'danger' | 'accent'
|
|
title?: string
|
|
disabled?: boolean
|
|
loading?: boolean
|
|
}) {
|
|
const variants = {
|
|
default: "text-foreground-muted hover:text-foreground hover:bg-foreground/5 border-transparent",
|
|
danger: "text-foreground-muted hover:text-red-400 hover:bg-red-500/10 border-transparent hover:border-red-500/20",
|
|
accent: "text-accent bg-accent/10 border-accent/20 hover:bg-accent/20",
|
|
}
|
|
|
|
return (
|
|
<button
|
|
onClick={(e) => {
|
|
e.stopPropagation()
|
|
onClick?.()
|
|
}}
|
|
disabled={disabled || loading}
|
|
title={title}
|
|
className={clsx(
|
|
"p-2 rounded-lg border transition-all duration-200",
|
|
"disabled:opacity-30 disabled:cursor-not-allowed",
|
|
variants[variant]
|
|
)}
|
|
>
|
|
{loading ? (
|
|
<Loader2 className="w-4 h-4 animate-spin" />
|
|
) : (
|
|
<Icon className="w-4 h-4" />
|
|
)}
|
|
</button>
|
|
)
|
|
}
|
|
|
|
// ============================================================================
|
|
// PLATFORM BADGE (for auctions)
|
|
// ============================================================================
|
|
|
|
export function PlatformBadge({ platform }: { platform: string }) {
|
|
const colors: Record<string, string> = {
|
|
'GoDaddy': 'text-blue-400 bg-blue-400/10 border-blue-400/20',
|
|
'Sedo': 'text-orange-400 bg-orange-400/10 border-orange-400/20',
|
|
'NameJet': 'text-purple-400 bg-purple-400/10 border-purple-400/20',
|
|
'DropCatch': 'text-teal-400 bg-teal-400/10 border-teal-400/20',
|
|
'ExpiredDomains': 'text-pink-400 bg-pink-400/10 border-pink-400/20',
|
|
}
|
|
|
|
return (
|
|
<span className={clsx(
|
|
"inline-flex items-center text-xs font-medium px-2 py-0.5 rounded-md border",
|
|
colors[platform] || "text-foreground-muted bg-foreground/5 border-border/50"
|
|
)}>
|
|
{platform}
|
|
</span>
|
|
)
|
|
}
|
|
|
|
// ============================================================================
|
|
// STAT CARD (for page headers)
|
|
// ============================================================================
|
|
|
|
export function StatCard({
|
|
title,
|
|
value,
|
|
subtitle,
|
|
icon: Icon,
|
|
accent = false,
|
|
trend,
|
|
}: {
|
|
title: string
|
|
value: string | number
|
|
subtitle?: string
|
|
icon?: React.ComponentType<{ className?: string }>
|
|
accent?: boolean
|
|
trend?: { value: number; label?: string }
|
|
}) {
|
|
return (
|
|
<div className={clsx(
|
|
"relative p-5 rounded-2xl border overflow-hidden transition-all duration-300",
|
|
accent
|
|
? "bg-gradient-to-br from-accent/15 to-accent/5 border-accent/30"
|
|
: "bg-gradient-to-br from-background-secondary/60 to-background-secondary/30 border-border/50 hover:border-accent/30"
|
|
)}>
|
|
{accent && <div className="absolute top-0 right-0 w-20 h-20 bg-accent/10 rounded-full blur-2xl" />}
|
|
<div className="relative">
|
|
{Icon && (
|
|
<div className={clsx(
|
|
"w-10 h-10 rounded-xl flex items-center justify-center mb-3",
|
|
accent ? "bg-accent/20 border border-accent/30" : "bg-foreground/5 border border-border/30"
|
|
)}>
|
|
<Icon className={clsx("w-5 h-5", accent ? "text-accent" : "text-foreground-muted")} />
|
|
</div>
|
|
)}
|
|
<p className="text-[10px] text-foreground-subtle uppercase tracking-wider mb-1">{title}</p>
|
|
<p className={clsx("text-2xl font-semibold", accent ? "text-accent" : "text-foreground")}>
|
|
{typeof value === 'number' ? value.toLocaleString() : value}
|
|
</p>
|
|
{subtitle && <p className="text-xs text-foreground-subtle mt-0.5">{subtitle}</p>}
|
|
{trend && (
|
|
<div className={clsx(
|
|
"inline-flex items-center gap-1 mt-2 text-xs font-medium px-2 py-0.5 rounded",
|
|
trend.value > 0 ? "text-accent bg-accent/10" : trend.value < 0 ? "text-red-400 bg-red-400/10" : "text-foreground-muted bg-foreground/5"
|
|
)}>
|
|
{trend.value > 0 ? '+' : ''}{trend.value}%
|
|
{trend.label && <span className="text-foreground-subtle">{trend.label}</span>}
|
|
</div>
|
|
)}
|
|
</div>
|
|
</div>
|
|
)
|
|
}
|
|
|
|
// ============================================================================
|
|
// PAGE CONTAINER (consistent max-width)
|
|
// ============================================================================
|
|
|
|
export function PageContainer({ children, className }: { children: ReactNode; className?: string }) {
|
|
return (
|
|
<div className={clsx("space-y-6", className)}>
|
|
{children}
|
|
</div>
|
|
)
|
|
}
|
|
|
|
// ============================================================================
|
|
// SECTION HEADER
|
|
// ============================================================================
|
|
|
|
export function SectionHeader({
|
|
title,
|
|
subtitle,
|
|
icon: Icon,
|
|
action,
|
|
compact = false,
|
|
}: {
|
|
title: string
|
|
subtitle?: string
|
|
icon?: React.ComponentType<{ className?: string }>
|
|
action?: ReactNode
|
|
compact?: boolean
|
|
}) {
|
|
return (
|
|
<div className={clsx("flex items-center justify-between", !compact && "mb-6")}>
|
|
<div className="flex items-center gap-3">
|
|
{Icon && (
|
|
<div className={clsx(
|
|
"bg-accent/10 border border-accent/20 rounded-xl flex items-center justify-center",
|
|
compact ? "w-9 h-9" : "w-10 h-10"
|
|
)}>
|
|
<Icon className={clsx(compact ? "w-4 h-4" : "w-5 h-5", "text-accent")} />
|
|
</div>
|
|
)}
|
|
<div>
|
|
<h2 className={clsx(compact ? "text-base" : "text-lg", "font-semibold text-foreground")}>{title}</h2>
|
|
{subtitle && <p className="text-sm text-foreground-muted">{subtitle}</p>}
|
|
</div>
|
|
</div>
|
|
{action}
|
|
</div>
|
|
)
|
|
}
|
|
|
|
// ============================================================================
|
|
// 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 (
|
|
<div className={clsx("relative", className)}>
|
|
<Search className="absolute left-3.5 top-1/2 -translate-y-1/2 w-4 h-4 text-foreground-muted" />
|
|
<input
|
|
type="text"
|
|
value={value}
|
|
onChange={(e) => 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) && (
|
|
<button
|
|
onClick={() => onClear ? onClear() : onChange('')}
|
|
className="absolute right-3 top-1/2 -translate-y-1/2 text-foreground-subtle hover:text-foreground transition-colors"
|
|
>
|
|
<X className="w-4 h-4" />
|
|
</button>
|
|
)}
|
|
</div>
|
|
)
|
|
}
|
|
|
|
// ============================================================================
|
|
// 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 (
|
|
<div className={clsx("flex flex-wrap items-center gap-1.5 p-1.5 bg-background-secondary/30 border border-border/30 rounded-xl w-fit", className)}>
|
|
{tabs.map((tab) => {
|
|
const isActive = activeTab === tab.id
|
|
const Icon = tab.icon
|
|
|
|
return (
|
|
<button
|
|
key={tab.id}
|
|
onClick={() => onChange(tab.id)}
|
|
className={clsx(
|
|
"flex items-center gap-2 px-3.5 py-2 text-sm font-medium rounded-lg transition-all",
|
|
isActive
|
|
? tab.color === 'warning'
|
|
? "bg-amber-500 text-background shadow-md"
|
|
: tab.color === 'accent'
|
|
? "bg-accent text-background shadow-md shadow-accent/20"
|
|
: "bg-foreground/10 text-foreground"
|
|
: "text-foreground-muted hover:text-foreground hover:bg-foreground/5"
|
|
)}
|
|
>
|
|
{Icon && <Icon className="w-4 h-4" />}
|
|
<span className="hidden sm:inline">{tab.label}</span>
|
|
{tab.count !== undefined && (
|
|
<span className={clsx(
|
|
"text-xs px-1.5 py-0.5 rounded-md tabular-nums",
|
|
isActive ? "bg-background/20" : "bg-foreground/10"
|
|
)}>
|
|
{tab.count}
|
|
</span>
|
|
)}
|
|
</button>
|
|
)
|
|
})}
|
|
</div>
|
|
)
|
|
}
|
|
|
|
// ============================================================================
|
|
// FILTER BAR (row of filters: search + select + buttons)
|
|
// ============================================================================
|
|
|
|
export function FilterBar({
|
|
children,
|
|
className,
|
|
}: {
|
|
children: ReactNode
|
|
className?: string
|
|
}) {
|
|
return (
|
|
<div className={clsx("flex flex-col sm:flex-row gap-3 sm:items-center", className)}>
|
|
{children}
|
|
</div>
|
|
)
|
|
}
|
|
|
|
// ============================================================================
|
|
// 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 (
|
|
<div className={clsx("relative", className)}>
|
|
<select
|
|
value={value}
|
|
onChange={(e) => onChange(e.target.value)}
|
|
className="h-10 pl-3.5 pr-9 bg-background-secondary/50 border border-border/40 rounded-xl
|
|
text-sm text-foreground appearance-none cursor-pointer
|
|
focus:outline-none focus:border-accent/50 focus:bg-background-secondary/80 transition-all"
|
|
>
|
|
{options.map((opt) => (
|
|
<option key={opt.value} value={opt.value}>{opt.label}</option>
|
|
))}
|
|
</select>
|
|
<ChevronDown className="absolute right-3 top-1/2 -translate-y-1/2 w-4 h-4 text-foreground-muted pointer-events-none" />
|
|
</div>
|
|
)
|
|
}
|
|
|
|
// ============================================================================
|
|
// 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 (
|
|
<button
|
|
onClick={onClick}
|
|
disabled={disabled}
|
|
className={clsx(
|
|
"flex items-center justify-center gap-2 font-medium rounded-xl transition-all",
|
|
"disabled:opacity-50 disabled:cursor-not-allowed",
|
|
size === 'small' ? "h-8 px-3 text-xs" : "h-10 px-4 text-sm",
|
|
variant === 'primary' && "bg-accent text-background hover:bg-accent-hover shadow-lg shadow-accent/20",
|
|
variant === 'secondary' && "bg-foreground/10 text-foreground hover:bg-foreground/15 border border-border/40",
|
|
variant === 'ghost' && "text-foreground-muted hover:text-foreground hover:bg-foreground/5",
|
|
className
|
|
)}
|
|
>
|
|
{Icon && <Icon className={size === 'small' ? "w-3.5 h-3.5" : "w-4 h-4"} />}
|
|
{children}
|
|
</button>
|
|
)
|
|
}
|
|
|