website refactor
This commit is contained in:
122
apps/website/ui/PresetCard.tsx
Normal file
122
apps/website/ui/PresetCard.tsx
Normal file
@@ -0,0 +1,122 @@
|
||||
'use client';
|
||||
|
||||
import type { MouseEventHandler, ReactNode } from 'react';
|
||||
import Card from './Card';
|
||||
|
||||
interface PresetCardStat {
|
||||
label: string;
|
||||
value: string;
|
||||
}
|
||||
|
||||
export interface PresetCardProps {
|
||||
title: string;
|
||||
subtitle?: string;
|
||||
primaryTag?: string;
|
||||
description?: string;
|
||||
stats?: PresetCardStat[];
|
||||
selected?: boolean;
|
||||
disabled?: boolean;
|
||||
onSelect?: () => void;
|
||||
className?: string;
|
||||
children?: ReactNode;
|
||||
}
|
||||
|
||||
export default function PresetCard({
|
||||
title,
|
||||
subtitle,
|
||||
primaryTag,
|
||||
description,
|
||||
stats,
|
||||
selected,
|
||||
disabled,
|
||||
onSelect,
|
||||
className = '',
|
||||
children,
|
||||
}: PresetCardProps) {
|
||||
const isInteractive = typeof onSelect === 'function' && !disabled;
|
||||
|
||||
const handleClick: MouseEventHandler<HTMLButtonElement | HTMLDivElement> = (event) => {
|
||||
if (!isInteractive) {
|
||||
return;
|
||||
}
|
||||
event.preventDefault();
|
||||
onSelect?.();
|
||||
};
|
||||
|
||||
const baseBorder = selected ? 'border-primary-blue' : 'border-charcoal-outline';
|
||||
const baseBg = selected ? 'bg-primary-blue/10' : 'bg-iron-gray';
|
||||
const baseRing = selected ? 'ring-2 ring-primary-blue/40' : '';
|
||||
const disabledClasses = disabled ? 'opacity-60 cursor-not-allowed' : '';
|
||||
const hoverClasses = isInteractive && !disabled ? 'hover:bg-iron-gray/80 hover:scale-[1.01]' : '';
|
||||
|
||||
const content = (
|
||||
<div className="flex h-full flex-col gap-3">
|
||||
<div className="flex items-start justify-between gap-3">
|
||||
<div>
|
||||
<div className="text-sm font-semibold text-white">{title}</div>
|
||||
{subtitle && (
|
||||
<div className="mt-0.5 text-xs text-gray-400">{subtitle}</div>
|
||||
)}
|
||||
</div>
|
||||
<div className="flex flex-col items-end gap-1">
|
||||
{primaryTag && (
|
||||
<span className="inline-flex rounded-full bg-primary-blue/15 px-2 py-0.5 text-[10px] font-medium uppercase tracking-wide text-primary-blue">
|
||||
{primaryTag}
|
||||
</span>
|
||||
)}
|
||||
{selected && (
|
||||
<span className="inline-flex items-center gap-1 rounded-full bg-primary-blue/10 px-2 py-0.5 text-[10px] font-medium text-primary-blue">
|
||||
<span className="h-1.5 w-1.5 rounded-full bg-primary-blue" />
|
||||
Selected
|
||||
</span>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{description && (
|
||||
<p className="text-xs text-gray-300">{description}</p>
|
||||
)}
|
||||
|
||||
{children}
|
||||
|
||||
{stats && stats.length > 0 && (
|
||||
<div className="mt-1 border-t border-charcoal-outline/70 pt-2">
|
||||
<dl className="grid grid-cols-1 gap-2 text-[11px] text-gray-400 sm:grid-cols-3">
|
||||
{stats.map((stat) => (
|
||||
<div key={stat.label} className="space-y-0.5">
|
||||
<dt className="font-medium text-gray-500">{stat.label}</dt>
|
||||
<dd className="text-xs text-gray-200">{stat.value}</dd>
|
||||
</div>
|
||||
))}
|
||||
</dl>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
|
||||
const commonClasses = `${baseBorder} ${baseBg} ${baseRing} ${hoverClasses} ${disabledClasses} ${className}`;
|
||||
|
||||
if (isInteractive) {
|
||||
return (
|
||||
<button
|
||||
type="button"
|
||||
onClick={handleClick as MouseEventHandler<HTMLButtonElement>}
|
||||
disabled={disabled}
|
||||
className={`group block w-full rounded-lg text-left text-sm shadow-card outline-none transition-all duration-150 focus-visible:outline focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-primary-blue ${commonClasses}`}
|
||||
>
|
||||
<div className="p-4">
|
||||
{content}
|
||||
</div>
|
||||
</button>
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
<Card
|
||||
className={commonClasses}
|
||||
onClick={handleClick as MouseEventHandler<HTMLDivElement>}
|
||||
>
|
||||
{content}
|
||||
</Card>
|
||||
);
|
||||
}
|
||||
Reference in New Issue
Block a user