feature: button and badge components updated.

This commit is contained in:
2026-05-06 02:20:19 +03:30
parent adfb5b436e
commit 6258477860
8 changed files with 89 additions and 83 deletions

View File

@@ -1,31 +1,59 @@
//src/components/ui/Badge.tsx
import React from 'react';
type BadgeVariant = 'success' | 'warning' | 'danger' | 'default';
export type BadgeVariant = 'success' | 'warning' | 'danger' | 'default';
interface BadgeProps {
children: React.ReactNode;
variant?: BadgeVariant;
className?: string;
children: React.ReactNode;
variant?: BadgeVariant;
className?: string;
/**
* Same pixel width for every badge (table columns).
* Set false only when the pill should shrink to the label.
*/
fixedWidth?: boolean;
}
const variantStyles: Record<BadgeVariant, string> = {
success: 'bg-emerald-900/30 text-emerald-300 border-emerald-700/60',
warning: 'bg-amber-900/30 text-amber-300 border-amber-700/60',
danger: 'bg-red-950/30 text-red-300 border-red-700/60',
default: 'bg-background-secondary text-text-secondary border-border',
success: 'bg-emerald-900/30 text-emerald-300 border-emerald-700/60',
warning: 'bg-amber-900/30 text-amber-300 border-amber-700/60',
danger: 'bg-red-950/30 text-red-300 border-red-700/60',
default: 'bg-background-secondary text-text-secondary border-border',
};
/** Explicit width + height so every row matches; flex centers label optically. */
const FIXED_LAYOUT_CLASS =
'w-[8rem] min-w-[8rem] max-w-[8rem] shrink-0 h-7 px-2 py-0';
export function Badge({
children,
variant = 'default',
className,
children,
variant = 'default',
className,
fixedWidth = true,
}: BadgeProps) {
return (
<span
className={`px-2 py-1 text-xs font-medium rounded-lg border inline-block ${variantStyles[variant]} ${className || ''}`}
>
{children}
</span>
);
}
const layoutClass = fixedWidth
? `${FIXED_LAYOUT_CLASS} justify-center text-center`
: 'min-h-[1.75rem] px-2.5 py-1 justify-center';
return (
<span
className={`inline-flex items-center box-border rounded-md border text-xs font-medium leading-none whitespace-nowrap ${variantStyles[variant]} ${layoutClass} ${className ?? ''}`}
>
{children}
</span>
);
}
/** Map organization link / invitation row status to badge variant. */
export function organizationLinkStatusVariant(status: string): BadgeVariant {
switch (status) {
case 'ACTIVE':
return 'success';
case 'PENDING':
return 'warning';
case 'REJECTED':
case 'EXPIRED':
return 'danger';
default:
return 'default';
}
}

View File

@@ -1,6 +1,4 @@
// src/components/ui/Button.tsx
import React from 'react';
import { Loader2 } from 'lucide-react';
type ButtonVariant = 'primary' | 'secondary' | 'outline' | 'danger' | 'ghost';
type ButtonSize = 'sm' | 'md' | 'lg';
@@ -25,23 +23,22 @@ export const Button: React.FC<ButtonProps> = ({
}) => {
const baseClasses =
'inline-flex items-center justify-center rounded-[var(--radius-md)] font-medium transition-all duration-200 ' +
'focus:outline-none focus:ring-2 focus:ring-primary/40 disabled:opacity-50 disabled:cursor-not-allowed';
'focus:outline-none focus:ring-2 focus:ring-primary/40 disabled:cursor-not-allowed';
const variantClasses: Record<ButtonVariant, string> = {
primary:
'bg-primary text-primary-contrast hover:brightness-105 shadow-[0_0_0_1px_var(--color-primary-soft)]',
'bg-primary text-white hover:opacity-90 disabled:opacity-60',
secondary:
'bg-surface-elevated text-text-primary border border-border hover:border-border-strong',
'bg-surface-elevated text-text-primary border border-border hover:border-border-strong disabled:opacity-50',
outline:
'border border-border text-text-primary hover:bg-background-card/70',
'border border-border text-text-primary hover:bg-background-card/70 disabled:opacity-50',
danger:
'bg-red-600 text-white hover:bg-red-700',
danger: 'bg-red-600 text-white hover:bg-red-700 disabled:opacity-50',
ghost:
'text-text-secondary hover:text-text-primary hover:bg-background-card/70',
'text-text-secondary hover:text-text-primary hover:bg-background-card/70 disabled:opacity-50',
};
const sizeClasses: Record<ButtonSize, string> = {
@@ -51,17 +48,16 @@ export const Button: React.FC<ButtonProps> = ({
};
const widthClass = fullWidth ? 'w-full' : '';
const loadingClass = isLoading ? 'opacity-70 animate-pulse pointer-events-none' : '';
return (
<button
className={`${baseClasses} ${variantClasses[variant]} ${sizeClasses[size]} ${widthClass} ${className}`}
className={`${baseClasses} ${variantClasses[variant]} ${sizeClasses[size]} ${widthClass} ${loadingClass} ${className}`}
disabled={disabled || isLoading}
aria-busy={isLoading || undefined}
{...props}
>
{isLoading && (
<Loader2 className="w-4 h-4 mr-2 animate-spin" />
)}
{children}
</button>
);
};
};

View File

@@ -2,7 +2,7 @@
import { useState } from 'react';
import { useAuth } from '@/lib/hooks/useAuth';
import { Building2, Beaker, Mail, Plus } from 'lucide-react';
import { Building2, Beaker, Mail } from 'lucide-react';
import { Input } from '@/components/ui/common/Input';
import { Button } from '@/components/ui/common/Button';
@@ -55,7 +55,6 @@ export function OrganizationSelectorContent() {
setIsCreateOpen((prev) => !prev);
}}
>
<Plus className="h-4 w-4 mr-2 icon-flat" />
{isCreateOpen ? 'Cancel' : 'Create Organization'}
</Button>
</div>