claude/sleepy-shirley-adfe57 #2
@@ -4,22 +4,60 @@ import { cva, type VariantProps } from 'class-variance-authority';
|
||||
import { cn } from '@/lib/utils';
|
||||
|
||||
const buttonVariants = cva(
|
||||
'inline-flex items-center justify-center gap-2 whitespace-nowrap text-sm font-medium transition-colors focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-[var(--accent-soft)] focus-visible:ring-offset-2 disabled:pointer-events-none disabled:opacity-50 [&_svg]:pointer-events-none [&_svg]:size-4 [&_svg]:shrink-0',
|
||||
[
|
||||
'inline-flex items-center justify-center gap-2 whitespace-nowrap font-semibold',
|
||||
'select-none cursor-pointer',
|
||||
'transition-all duration-150 ease-out',
|
||||
'focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-[var(--accent)] focus-visible:ring-offset-2 focus-visible:ring-offset-[var(--bg)]',
|
||||
'disabled:pointer-events-none disabled:opacity-40',
|
||||
'[&_svg]:pointer-events-none [&_svg]:shrink-0',
|
||||
'active:scale-[0.96] active:brightness-95',
|
||||
].join(' '),
|
||||
{
|
||||
variants: {
|
||||
variant: {
|
||||
default: 'bg-[var(--accent)] text-white shadow-sm hover:bg-[var(--accent-hover)]',
|
||||
destructive: 'bg-red-500 text-white shadow-sm hover:bg-red-600',
|
||||
outline: 'border border-[var(--border)] bg-[var(--surface)] shadow-sm hover:bg-[var(--surface-hover)] text-[var(--fg)]',
|
||||
secondary: 'bg-[var(--border-light)] text-[var(--fg)] shadow-sm hover:bg-[var(--border)]',
|
||||
ghost: 'hover:bg-[var(--border-light)] text-[var(--fg-secondary)]',
|
||||
link: 'text-[var(--accent)] underline-offset-4 hover:underline',
|
||||
// Основная — с градиентом и тенью
|
||||
default: [
|
||||
'bg-[var(--accent)] text-white',
|
||||
'shadow-[0_1px_2px_rgba(0,0,0,0.25),inset_0_1px_0_rgba(255,255,255,0.15)]',
|
||||
'hover:bg-[var(--accent-hover)] hover:shadow-[0_3px_10px_rgba(201,68,93,0.4)]',
|
||||
'hover:-translate-y-px',
|
||||
].join(' '),
|
||||
|
||||
// Деструктивная
|
||||
destructive: [
|
||||
'bg-red-500 text-white',
|
||||
'shadow-[0_1px_2px_rgba(0,0,0,0.25),inset_0_1px_0_rgba(255,255,255,0.12)]',
|
||||
'hover:bg-red-600 hover:shadow-[0_3px_10px_rgba(239,68,68,0.4)]',
|
||||
'hover:-translate-y-px',
|
||||
].join(' '),
|
||||
|
||||
// Обводка — хорошо видна и в тёмной теме
|
||||
outline: [
|
||||
'border-2 border-[var(--border)] bg-transparent text-[var(--fg)]',
|
||||
'hover:border-[var(--fg-tertiary)] hover:bg-[var(--surface-hover)]',
|
||||
].join(' '),
|
||||
|
||||
// Вторичная — мягкая подложка
|
||||
secondary: [
|
||||
'bg-[var(--border-light)] text-[var(--fg-secondary)]',
|
||||
'hover:bg-[var(--border)] hover:text-[var(--fg)]',
|
||||
].join(' '),
|
||||
|
||||
// Призрак
|
||||
ghost: [
|
||||
'bg-transparent text-[var(--fg-secondary)]',
|
||||
'hover:bg-[var(--border-light)] hover:text-[var(--fg)]',
|
||||
].join(' '),
|
||||
|
||||
// Ссылка
|
||||
link: 'bg-transparent text-[var(--accent)] underline-offset-4 hover:underline p-0 h-auto',
|
||||
},
|
||||
size: {
|
||||
default: 'h-9 rounded-xl px-4 py-2',
|
||||
sm: 'h-8 rounded-lg px-3 text-xs',
|
||||
lg: 'h-10 rounded-xl px-6',
|
||||
icon: 'h-9 w-9 rounded-xl',
|
||||
default: 'h-10 rounded-xl px-5 py-2 text-sm',
|
||||
sm: 'h-8 rounded-lg px-3.5 text-xs',
|
||||
lg: 'h-12 rounded-xl px-7 text-[15px]',
|
||||
icon: 'h-9 w-9 rounded-xl text-sm',
|
||||
},
|
||||
},
|
||||
defaultVariants: {
|
||||
@@ -38,7 +76,13 @@ export interface ButtonProps
|
||||
const Button = React.forwardRef<HTMLButtonElement, ButtonProps>(
|
||||
({ className, variant, size, asChild = false, ...props }, ref) => {
|
||||
const Comp = asChild ? Slot : 'button';
|
||||
return <Comp className={cn(buttonVariants({ variant, size, className }))} ref={ref} {...props} />;
|
||||
return (
|
||||
<Comp
|
||||
className={cn(buttonVariants({ variant, size, className }))}
|
||||
ref={ref}
|
||||
{...props}
|
||||
/>
|
||||
);
|
||||
}
|
||||
);
|
||||
Button.displayName = 'Button';
|
||||
|
||||
@@ -80,7 +80,7 @@ export default function FeedPage() {
|
||||
</div>
|
||||
<p className="text-base font-semibold mb-1">Не удалось загрузить</p>
|
||||
<p className="text-sm text-[var(--fg-tertiary)] mb-5">Попробуйте снова</p>
|
||||
<Button variant="outline" onClick={() => refetch()} className="rounded-xl text-xs gap-1.5 h-9 px-4">
|
||||
<Button variant="outline" size="sm" onClick={() => refetch()} className="gap-1.5">
|
||||
<RefreshCw className="h-3.5 w-3.5" strokeWidth={1.5} /> Попробовать
|
||||
</Button>
|
||||
</div>
|
||||
@@ -94,7 +94,7 @@ export default function FeedPage() {
|
||||
<p className="text-base font-semibold mb-1">Пока нет котов</p>
|
||||
<p className="text-sm text-[var(--fg-tertiary)] mb-5">Будьте первым</p>
|
||||
<Button onClick={() => window.location.href = '/upload'}
|
||||
className="rounded-xl text-xs h-9 px-5 bg-[var(--accent)] hover:bg-[var(--accent-hover)] text-white">
|
||||
size="sm">
|
||||
Загрузить кота
|
||||
</Button>
|
||||
</div>
|
||||
|
||||
@@ -71,7 +71,7 @@ export default function LoginPage() {
|
||||
<Button
|
||||
type="submit"
|
||||
disabled={loading}
|
||||
className="w-full h-12 rounded-xl text-sm font-semibold bg-[var(--accent)] hover:bg-[var(--accent-hover)] text-white transition-all active:scale-[0.98] shadow-sm"
|
||||
size="lg" className="w-full"
|
||||
>
|
||||
{loading ? (
|
||||
<span className="flex items-center gap-2">
|
||||
|
||||
@@ -167,7 +167,7 @@ export default function ProfilePage() {
|
||||
<Link to="/upload">
|
||||
<Button
|
||||
size="sm"
|
||||
className="rounded-xl text-xs h-8 gap-1.5 bg-[var(--accent)] hover:bg-[var(--accent-hover)] text-white px-4 shadow-sm"
|
||||
size="sm" className="gap-1.5"
|
||||
>
|
||||
<Plus className="h-3.5 w-3.5" strokeWidth={2.5} />
|
||||
Новая публикация
|
||||
@@ -198,7 +198,7 @@ export default function ProfilePage() {
|
||||
</p>
|
||||
{isMe && (
|
||||
<Link to="/upload">
|
||||
<Button size="sm" className="rounded-xl text-xs bg-[var(--accent)] hover:bg-[var(--accent-hover)] text-white">
|
||||
<Button size="sm">
|
||||
Загрузить кота
|
||||
</Button>
|
||||
</Link>
|
||||
|
||||
@@ -79,7 +79,7 @@ export default function RegisterPage() {
|
||||
<Button
|
||||
type="submit"
|
||||
disabled={loading}
|
||||
className="w-full h-12 rounded-xl text-sm font-semibold bg-[var(--accent)] hover:bg-[var(--accent-hover)] text-white transition-all active:scale-[0.98] shadow-sm mt-1"
|
||||
size="lg" className="w-full mt-1"
|
||||
>
|
||||
{loading ? (
|
||||
<span className="flex items-center gap-2">
|
||||
|
||||
@@ -89,7 +89,7 @@ export default function UploadPage() {
|
||||
<p className="text-xs text-[var(--fg-tertiary)] mb-6">
|
||||
или нажмите, чтобы выбрать файл
|
||||
</p>
|
||||
<Button variant="outline" size="sm" className="rounded-xl text-xs px-6 h-9">
|
||||
<Button variant="outline" size="sm" className="px-6">
|
||||
Выбрать файл
|
||||
</Button>
|
||||
<p className="mt-5 text-[10px] text-[var(--fg-tertiary)] tracking-wide">
|
||||
@@ -156,7 +156,7 @@ export default function UploadPage() {
|
||||
onClick={handleUpload}
|
||||
disabled={uploadMutation.isPending}
|
||||
size="sm"
|
||||
className="rounded-xl text-sm px-7 h-10 bg-[var(--accent)] hover:bg-[var(--accent-hover)] text-white disabled:opacity-60 shadow-sm"
|
||||
className="px-7"
|
||||
>
|
||||
{uploadMutation.isPending ? (
|
||||
<span className="flex items-center gap-2">
|
||||
|
||||
Reference in New Issue
Block a user