feat: add Badge, StatusDot, GhButton shared UI primitives
This commit is contained in:
31
src/components/ui/Badge.tsx
Normal file
31
src/components/ui/Badge.tsx
Normal file
@@ -0,0 +1,31 @@
|
|||||||
|
import { cn } from '@/lib/utils';
|
||||||
|
|
||||||
|
type BadgeVariant = 'green' | 'yellow' | 'red' | 'blue' | 'gray';
|
||||||
|
|
||||||
|
interface BadgeProps {
|
||||||
|
variant: BadgeVariant;
|
||||||
|
children: React.ReactNode;
|
||||||
|
className?: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
const variantClasses: Record<BadgeVariant, string> = {
|
||||||
|
green: 'bg-gh-green-bg text-gh-green border border-gh-green/20',
|
||||||
|
yellow: 'bg-gh-yellow-bg text-gh-yellow border border-gh-yellow/20',
|
||||||
|
red: 'bg-gh-red-bg text-gh-red border border-gh-red/20',
|
||||||
|
blue: 'bg-gh-blue-bg text-gh-blue border border-gh-blue/20',
|
||||||
|
gray: 'bg-gh-overlay text-gh-muted border border-gh-border',
|
||||||
|
};
|
||||||
|
|
||||||
|
export function Badge({ variant, children, className }: BadgeProps) {
|
||||||
|
return (
|
||||||
|
<span
|
||||||
|
className={cn(
|
||||||
|
'inline-flex items-center gap-1 rounded-full px-2 py-0.5 text-xs font-medium',
|
||||||
|
variantClasses[variant],
|
||||||
|
className
|
||||||
|
)}
|
||||||
|
>
|
||||||
|
{children}
|
||||||
|
</span>
|
||||||
|
);
|
||||||
|
}
|
||||||
47
src/components/ui/GhButton.tsx
Normal file
47
src/components/ui/GhButton.tsx
Normal file
@@ -0,0 +1,47 @@
|
|||||||
|
import { cn } from '@/lib/utils';
|
||||||
|
import { ButtonHTMLAttributes } from 'react';
|
||||||
|
|
||||||
|
type ButtonVariant = 'primary' | 'ghost' | 'danger-outline' | 'danger-solid';
|
||||||
|
type ButtonSize = 'sm' | 'md';
|
||||||
|
|
||||||
|
interface GhButtonProps extends ButtonHTMLAttributes<HTMLButtonElement> {
|
||||||
|
variant?: ButtonVariant;
|
||||||
|
size?: ButtonSize;
|
||||||
|
}
|
||||||
|
|
||||||
|
const variantClasses: Record<ButtonVariant, string> = {
|
||||||
|
primary: 'bg-[#238636] border border-[#2ea043] text-white hover:bg-[#2ea043]',
|
||||||
|
ghost: 'bg-transparent border border-gh-border text-gh-secondary hover:bg-gh-overlay hover:text-gh-primary',
|
||||||
|
'danger-outline': 'bg-transparent border border-gh-border text-gh-red hover:border-gh-red/40 hover:bg-gh-red-bg',
|
||||||
|
'danger-solid': 'bg-gh-overlay border border-gh-red/20 text-gh-red hover:bg-gh-red-bg',
|
||||||
|
};
|
||||||
|
|
||||||
|
const sizeClasses: Record<ButtonSize, string> = {
|
||||||
|
sm: 'px-3 py-1.5 text-xs',
|
||||||
|
md: 'px-4 py-2 text-sm',
|
||||||
|
};
|
||||||
|
|
||||||
|
export function GhButton({
|
||||||
|
variant = 'ghost',
|
||||||
|
size = 'md',
|
||||||
|
className,
|
||||||
|
children,
|
||||||
|
disabled,
|
||||||
|
...props
|
||||||
|
}: GhButtonProps) {
|
||||||
|
return (
|
||||||
|
<button
|
||||||
|
{...props}
|
||||||
|
disabled={disabled}
|
||||||
|
className={cn(
|
||||||
|
'inline-flex items-center justify-center rounded-md font-medium transition-colors focus:outline-none focus:ring-2 focus:ring-gh-blue focus:ring-offset-1 focus:ring-offset-gh-base',
|
||||||
|
'disabled:cursor-not-allowed disabled:opacity-40',
|
||||||
|
variantClasses[variant],
|
||||||
|
sizeClasses[size],
|
||||||
|
className
|
||||||
|
)}
|
||||||
|
>
|
||||||
|
{children}
|
||||||
|
</button>
|
||||||
|
);
|
||||||
|
}
|
||||||
28
src/components/ui/StatusDot.tsx
Normal file
28
src/components/ui/StatusDot.tsx
Normal file
@@ -0,0 +1,28 @@
|
|||||||
|
import { ServiceStatus } from '@/lib/schemas/server';
|
||||||
|
import { cn } from '@/lib/utils';
|
||||||
|
|
||||||
|
interface StatusDotProps {
|
||||||
|
status: ServiceStatus;
|
||||||
|
className?: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
export function StatusDot({ status, className }: StatusDotProps) {
|
||||||
|
const dotClass = {
|
||||||
|
[ServiceStatus.Running]: 'bg-gh-green shadow-[0_0_0_3px_#3fb95022]',
|
||||||
|
[ServiceStatus.Starting]: 'bg-gh-yellow animate-pulse',
|
||||||
|
[ServiceStatus.Stopping]: 'bg-gh-yellow animate-pulse',
|
||||||
|
[ServiceStatus.Restarting]: 'bg-gh-yellow animate-pulse',
|
||||||
|
[ServiceStatus.Stopped]: 'bg-gh-subtle',
|
||||||
|
[ServiceStatus.Unknown]: 'bg-gh-subtle',
|
||||||
|
}[status] ?? 'bg-gh-subtle';
|
||||||
|
|
||||||
|
return (
|
||||||
|
<span
|
||||||
|
className={cn(
|
||||||
|
'inline-block h-2 w-2 rounded-full flex-shrink-0',
|
||||||
|
dotClass,
|
||||||
|
className
|
||||||
|
)}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user