Files
pos-system/apps/web-admin/src/components/ui/button.tsx
Ho Ngoc Hai 5ce05c63e7 Enhance web-admin application with internationalization and UI improvements
- 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.
2026-01-02 10:36:38 +07:00

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 };