- Integrated `@hookform/resolvers` and `@radix-ui/react-avatar` for improved form handling and avatar components. - Updated Tailwind CSS configuration and global styles for better responsiveness and accessibility. - Refactored layout components to include `I18nProvider` for dynamic language support. - Enhanced various UI elements with translated strings for better user experience. - Improved error handling and validation messages in forms to support localization. These changes aim to provide a more inclusive and user-friendly experience in the web-admin application.
133 lines
5.1 KiB
TypeScript
133 lines
5.1 KiB
TypeScript
import * as React from 'react';
|
|
import { cva, type VariantProps } from 'class-variance-authority';
|
|
import { cn } from '@/lib/utils';
|
|
|
|
/**
|
|
* EN: Button component variants configuration using class-variance-authority
|
|
* VI: Cấu hình các biến thể của component Button sử dụng class-variance-authority
|
|
*/
|
|
const buttonVariants = cva(
|
|
// EN: Base styles for all button variants / VI: Styles cơ bản cho tất cả các biến thể button
|
|
'inline-flex items-center justify-center rounded-md font-medium transition-all duration-[150ms] ease-out focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-offset-2 focus-visible:ring-offset-bg-primary disabled:pointer-events-none disabled:opacity-50 active:scale-[0.98]',
|
|
{
|
|
variants: {
|
|
variant: {
|
|
// EN: Primary button - main CTA style / VI: Button chính - style CTA chính
|
|
primary:
|
|
'bg-accent-primary text-white hover:brightness-110 hover:scale-[1.02] active:scale-[0.98] active:brightness-90 focus-visible:ring-accent-primary focus-visible:shadow-[0_0_20px_rgba(59,130,246,0.3)] shadow-md hover:shadow-lg',
|
|
// EN: Secondary button - alternative style / VI: Button phụ - style thay thế
|
|
secondary:
|
|
'bg-chat-ai-bubble text-chat-ai-text hover:bg-bg-tertiary hover:scale-[1.02] active:scale-[0.98] active:brightness-90 focus-visible:ring-accent-primary focus-visible:shadow-[0_0_20px_rgba(59,130,246,0.3)] border border-border-primary',
|
|
// EN: Ghost button - minimal style / VI: Button ghost - style tối giản
|
|
ghost:
|
|
'text-text-secondary hover:bg-bg-tertiary hover:text-text-primary active:bg-bg-elevated focus-visible:ring-accent-primary',
|
|
// EN: Danger button - destructive actions / VI: Button nguy hiểm - hành động phá hủy
|
|
danger:
|
|
'bg-accent-error text-white hover:brightness-110 hover:scale-[1.02] active:scale-[0.98] active:brightness-90 focus-visible:ring-accent-error focus-visible:shadow-[0_0_20px_rgba(239,68,68,0.3)] shadow-md hover:shadow-lg',
|
|
},
|
|
size: {
|
|
// EN: Extra small button - 28px height (mobile: min 44px) / VI: Button cực nhỏ - chiều cao 28px (mobile: tối thiểu 44px)
|
|
xs: 'h-7 px-2 text-xs min-h-[44px] min-w-[44px]',
|
|
// EN: Small button - 32px height (mobile: min 44px) / VI: Button nhỏ - chiều cao 32px (mobile: tối thiểu 44px)
|
|
sm: 'h-8 px-3 text-sm min-h-[44px] min-w-[44px]',
|
|
// EN: Medium button (default) - 40px height (mobile: min 44px) / VI: Button trung bình (mặc định) - chiều cao 40px (mobile: tối thiểu 44px)
|
|
md: 'h-10 px-4 text-base min-h-[44px] min-w-[44px]',
|
|
// EN: Large button - 48px height / VI: Button lớn - chiều cao 48px
|
|
lg: 'h-12 px-6 text-lg min-h-[44px] min-w-[44px]',
|
|
// EN: Extra large button - 56px height / VI: Button cực lớn - chiều cao 56px
|
|
xl: 'h-14 px-8 text-xl min-h-[44px] min-w-[44px]',
|
|
},
|
|
},
|
|
defaultVariants: {
|
|
variant: 'primary',
|
|
size: 'md',
|
|
},
|
|
}
|
|
);
|
|
|
|
/**
|
|
* EN: Button component props interface
|
|
* VI: Interface cho props của component Button
|
|
*/
|
|
export interface ButtonProps
|
|
extends React.ButtonHTMLAttributes<HTMLButtonElement>,
|
|
VariantProps<typeof buttonVariants> {
|
|
/**
|
|
* EN: Loading state - shows spinner when true / VI: Trạng thái loading - hiển thị spinner khi true
|
|
*/
|
|
loading?: boolean;
|
|
/**
|
|
* EN: Button content / VI: Nội dung button
|
|
*/
|
|
children?: React.ReactNode;
|
|
}
|
|
|
|
/**
|
|
* EN: Button component with variants and sizes
|
|
* VI: Component Button với các biến thể và kích thước
|
|
*
|
|
* @example
|
|
* ```tsx
|
|
* <Button variant="primary" size="md">Click me</Button>
|
|
* <Button variant="secondary" size="lg" loading>Loading...</Button>
|
|
* <Button variant="ghost" size="sm">Cancel</Button>
|
|
* <Button variant="danger" size="md">Delete</Button>
|
|
* ```
|
|
*/
|
|
const Button = React.forwardRef<HTMLButtonElement, ButtonProps>(
|
|
(
|
|
{
|
|
className,
|
|
variant,
|
|
size,
|
|
loading = false,
|
|
disabled,
|
|
children,
|
|
...props
|
|
},
|
|
ref
|
|
) => {
|
|
return (
|
|
<button
|
|
className={cn(buttonVariants({ variant, size }), className)}
|
|
ref={ref}
|
|
disabled={disabled || loading}
|
|
{...props}
|
|
>
|
|
{loading ? (
|
|
<>
|
|
{/* EN: Loading spinner / VI: Spinner loading */}
|
|
<svg
|
|
className="mr-2 h-4 w-4 animate-spin"
|
|
xmlns="http://www.w3.org/2000/svg"
|
|
fill="none"
|
|
viewBox="0 0 24 24"
|
|
>
|
|
<circle
|
|
className="opacity-25"
|
|
cx="12"
|
|
cy="12"
|
|
r="10"
|
|
stroke="currentColor"
|
|
strokeWidth="4"
|
|
/>
|
|
<path
|
|
className="opacity-75"
|
|
fill="currentColor"
|
|
d="M4 12a8 8 0 018-8V0C5.373 0 0 5.373 0 12h4zm2 5.291A7.962 7.962 0 014 12H0c0 3.042 1.135 5.824 3 7.938l3-2.647z"
|
|
/>
|
|
</svg>
|
|
{children}
|
|
</>
|
|
) : (
|
|
children
|
|
)}
|
|
</button>
|
|
);
|
|
}
|
|
);
|
|
|
|
Button.displayName = 'Button';
|
|
|
|
export { Button, buttonVariants };
|