From 5a71b1132b79405687ea7fd2e6ea918d1e0d72c9 Mon Sep 17 00:00:00 2001 From: Ho Ngoc Hai Date: Sun, 4 Jan 2026 16:56:44 +0700 Subject: [PATCH] =?UTF-8?q?feat:=20Th=C3=AAm=20c=C3=A1c=20component=20Popo?= =?UTF-8?q?ver,=20Tooltip=20v=C3=A0=20refactor=20Switch=20s=E1=BB=AD=20d?= =?UTF-8?q?=E1=BB=A5ng=20Radix=20UI,=20=C4=91=E1=BB=93ng=20th=E1=BB=9Di=20?= =?UTF-8?q?c=E1=BA=ADp=20nh=E1=BA=ADt=20bi=E1=BA=BFn=20m=C3=A0u=20v=C3=A0?= =?UTF-8?q?=20dependencies.?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- apps/web-client/package.json | 3 + apps/web-client/src/app/globals.css | 77 +-- apps/web-client/src/app/layout.tsx | 12 +- apps/web-client/src/components/ui/popover.tsx | 39 ++ apps/web-client/src/components/ui/switch.tsx | 123 ++--- apps/web-client/src/components/ui/tooltip.tsx | 34 ++ apps/web-client/src/styles/theme.css | 515 +++++++++--------- pnpm-lock.yaml | 133 +++++ 8 files changed, 521 insertions(+), 415 deletions(-) create mode 100644 apps/web-client/src/components/ui/popover.tsx create mode 100644 apps/web-client/src/components/ui/tooltip.tsx diff --git a/apps/web-client/package.json b/apps/web-client/package.json index 7748a1bf..858f447a 100644 --- a/apps/web-client/package.json +++ b/apps/web-client/package.json @@ -18,6 +18,9 @@ "@radix-ui/react-avatar": "^1.0.4", "@radix-ui/react-dialog": "^1.0.5", "@radix-ui/react-dropdown-menu": "^2.0.6", + "@radix-ui/react-popover": "^1.1.15", + "@radix-ui/react-switch": "^1.2.6", + "@radix-ui/react-tooltip": "^1.2.8", "@tanstack/react-query": "^5.17.0", "axios": "^1.6.5", "class-variance-authority": "^0.7.0", diff --git a/apps/web-client/src/app/globals.css b/apps/web-client/src/app/globals.css index 07b766c3..26264320 100644 --- a/apps/web-client/src/app/globals.css +++ b/apps/web-client/src/app/globals.css @@ -29,7 +29,8 @@ background-color: var(--bg-primary); color: var(--text-primary); font-size: var(--text-base); - line-height: 1.5; + font-weight: var(--font-light); + line-height: var(--leading-relaxed); transition: background-color var(--duration-normal) var(--ease-in-out), color var(--duration-normal) var(--ease-in-out); } @@ -89,55 +90,7 @@ */ body { font-size: 16px; - min-font-size: 16px; } - - /** - * EN: Support zoom up to 200% without breaking layout - WCAG 2.1 AA - * VI: Hỗ trợ zoom lên đến 200% mà không làm vỡ layout - WCAG 2.1 AA - */ - html { - zoom: 1; - } - - /** - * EN: Ensure all text is zoomable up to 200% - WCAG 2.1 AA - * VI: Đảm bảo tất cả text có thể zoom lên đến 200% - WCAG 2.1 AA - */ - * { - max-width: 100%; - } - - /** - * EN: Prevent horizontal scroll on zoom - WCAG 2.1 AA - * VI: Ngăn scroll ngang khi zoom - WCAG 2.1 AA - */ - body { - overflow-x: hidden; - } -} - -/** - * EN: Typing Indicator Animation - * VI: Animation cho Typing Indicator - */ -@keyframes typing-pulse { - - 0%, - 60%, - 100% { - opacity: 0.3; - transform: scale(0.8); - } - - 30% { - opacity: 1; - transform: scale(1); - } -} - -.typing-dot { - /* EN: Animation properties are set inline to allow customization / VI: Các thuộc tính animation được set inline để cho phép tùy chỉnh */ } /** @@ -159,8 +112,8 @@ } /** - * EN: Floating Animation - Creates a weightless hovering effect - * VI: Animation lơ lửng - Tạo hiệu ứng nhấp nhô không trọng lực + * EN: Floating Animation + * VI: Animation lơ lửng */ @keyframes float { 0% { @@ -177,8 +130,8 @@ } /** - * EN: Mesh Gradient Animation - Creates dynamic moving color spots - * VI: Animation Mesh Gradient - Tạo các đốm màu di chuyển động + * EN: Mesh Gradient Animation + * VI: Animation Mesh Gradient */ @keyframes mesh-move { 0% { @@ -199,8 +152,8 @@ } /** - * EN: Shimmer animation for skeleton loaders - * VI: Animation shimmer cho skeleton loaders + * EN: Shimmer animation + * VI: Animation shimmer */ @keyframes shimmer { 0% { @@ -213,8 +166,8 @@ } /** - * EN: Ensure smooth animations and prevent layout shift - * VI: Đảm bảo animation mượt mà và ngăn layout shift + * EN: Utilities Layer + * VI: Layer Utilities */ @layer utilities { .animate-fadeIn { @@ -274,12 +227,6 @@ animation: shimmer 2s infinite; } - /* ============================================ - EN: Responsive Layout Utilities - VI: Utilities cho responsive layout - ============================================ */ - - /* Mobile-first container */ .container-responsive { width: 100%; margin-left: auto; @@ -316,21 +263,17 @@ } } - /* Mobile-optimized text sizes */ .text-responsive-hero { font-size: 2.5rem; - /* 40px mobile */ line-height: 1.1; } @media (min-width: 768px) { .text-responsive-hero { font-size: 3.75rem; - /* 60px desktop */ } } - /* Touch-friendly buttons on mobile */ .btn-touch { min-height: 44px; min-width: 44px; diff --git a/apps/web-client/src/app/layout.tsx b/apps/web-client/src/app/layout.tsx index 6390505a..3d9bf7ea 100644 --- a/apps/web-client/src/app/layout.tsx +++ b/apps/web-client/src/app/layout.tsx @@ -1,8 +1,18 @@ import type { Metadata } from 'next'; +import { Inter } from 'next/font/google'; import './globals.css'; import { ThemeProvider } from '../contexts/theme-context'; import { QueryProvider } from '../providers/query-provider'; import { I18nProvider } from '../providers/i18n-provider'; + +// EN: Configure Inter font with subsets and variable name +// VI: Cấu hình font Inter với các subsets và tên biến CSS +const inter = Inter({ + subsets: ['latin', 'vietnamese'], + display: 'swap', + variable: '--font-inter', + weight: ['100', '200', '300', '400', '500', '600', '700', '800', '900'], +}); import { SkipToContent } from '../components/accessibility/skip-to-content'; /** @@ -65,7 +75,7 @@ export default function RootLayout({ return ( // EN: Root HTML structure with dynamic language (will be updated by I18nProvider) // VI: Cấu trúc HTML gốc với ngôn ngữ động (sẽ được cập nhật bởi I18nProvider) - + diff --git a/apps/web-client/src/components/ui/popover.tsx b/apps/web-client/src/components/ui/popover.tsx new file mode 100644 index 00000000..794c1d61 --- /dev/null +++ b/apps/web-client/src/components/ui/popover.tsx @@ -0,0 +1,39 @@ +'use client'; + +import * as React from 'react'; +import * as PopoverPrimitive from '@radix-ui/react-popover'; +import { cn } from '@/lib/utils'; + +/** + * EN: Popover component - Floating content triggered by an element + * VI: Component Popover - Nội dung nổi được kích hoạt bởi một phần tử + */ +const Popover = PopoverPrimitive.Root; + +const PopoverTrigger = PopoverPrimitive.Trigger; + +const PopoverAnchor = PopoverPrimitive.Anchor; + +const PopoverContent = React.forwardRef< + React.ElementRef, + React.ComponentPropsWithoutRef +>(({ className, align = 'center', sideOffset = 4, ...props }, ref) => ( + + + +)); +PopoverContent.displayName = PopoverPrimitive.Content.displayName; + +export { Popover, PopoverTrigger, PopoverContent, PopoverAnchor }; diff --git a/apps/web-client/src/components/ui/switch.tsx b/apps/web-client/src/components/ui/switch.tsx index 0d936cb2..cae39acc 100644 --- a/apps/web-client/src/components/ui/switch.tsx +++ b/apps/web-client/src/components/ui/switch.tsx @@ -1,101 +1,40 @@ 'use client'; import * as React from 'react'; - +import * as SwitchPrimitives from '@radix-ui/react-switch'; import { cn } from '@/lib/utils'; /** - * EN: Switch component props interface - * VI: Interface cho props của component Switch + * EN: Switch component - A control that allows the user to toggle between checked and not checked + * VI: Component Switch - Bộ điều khiển cho phép người dùng chuyển đổi giữa trạng thái checked và không checked */ -export interface SwitchProps extends Omit, 'onChange'> { - /** - * EN: Whether the switch is checked / VI: Switch có được bật hay không - */ - checked?: boolean; - /** - * EN: Default checked state (uncontrolled) / VI: Trạng thái checked mặc định (uncontrolled) - */ - defaultChecked?: boolean; - /** - * EN: Callback when checked state changes / VI: Callback khi trạng thái checked thay đổi - */ - onCheckedChange?: (checked: boolean) => void; - /** - * EN: Whether the switch is disabled / VI: Switch có bị vô hiệu hóa hay không - */ - disabled?: boolean; -} - -/** - * EN: Switch component - Toggle switch for binary settings - * VI: Component Switch - Toggle switch cho các cài đặt nhị phân - * - * @example - * ```tsx - * - * - * ``` - */ -const Switch = React.forwardRef( - ({ className, checked, defaultChecked, onCheckedChange, disabled, ...props }, ref) => { - const [internalChecked, setInternalChecked] = React.useState(defaultChecked ?? false); - const isControlled = checked !== undefined; - const isChecked = isControlled ? checked : internalChecked; - - const handleClick = () => { - if (disabled) return; - const newChecked = !isChecked; - if (!isControlled) { - setInternalChecked(newChecked); - } - onCheckedChange?.(newChecked); - }; - - const handleKeyDown = (e: React.KeyboardEvent) => { - if (disabled) return; - if (e.key === 'Enter' || e.key === ' ') { - e.preventDefault(); - handleClick(); - } - }; - - return ( - - ); - } -); - -Switch.displayName = 'Switch'; +const Switch = React.forwardRef< + React.ElementRef, + React.ComponentPropsWithoutRef +>(({ className, ...props }, ref) => ( + + + +)); +Switch.displayName = SwitchPrimitives.Root.displayName; export { Switch }; diff --git a/apps/web-client/src/components/ui/tooltip.tsx b/apps/web-client/src/components/ui/tooltip.tsx new file mode 100644 index 00000000..8b416f37 --- /dev/null +++ b/apps/web-client/src/components/ui/tooltip.tsx @@ -0,0 +1,34 @@ +'use client'; + +import * as React from 'react'; +import * as TooltipPrimitive from '@radix-ui/react-tooltip'; +import { cn } from '@/lib/utils'; + +/** + * EN: Tooltip component - Informational text on hover or focus + * VI: Component Tooltip - Văn bản thông tin khi hover hoặc focus + */ +const TooltipProvider = TooltipPrimitive.Provider; + +const Tooltip = TooltipPrimitive.Root; + +const TooltipTrigger = TooltipPrimitive.Trigger; + +const TooltipContent = React.forwardRef< + React.ElementRef, + React.ComponentPropsWithoutRef +>(({ className, sideOffset = 4, ...props }, ref) => ( + +)); +TooltipContent.displayName = TooltipPrimitive.Content.displayName; + +export { Tooltip, TooltipTrigger, TooltipContent, TooltipProvider }; diff --git a/apps/web-client/src/styles/theme.css b/apps/web-client/src/styles/theme.css index 2bfd1fd6..61869de9 100644 --- a/apps/web-client/src/styles/theme.css +++ b/apps/web-client/src/styles/theme.css @@ -14,305 +14,310 @@ */ :root { - /* ============================================ + /* ============================================ EN: Color Palette - Dark Mode (Primary Theme) VI: Bảng màu - Dark Mode (Theme chính) ============================================ */ - /* Background Colors / Màu nền */ - --bg-primary: #000000; - /* Pure black - Main background */ - --bg-secondary: #0A0A0A; - /* Almost black - Card/Panel background */ - --bg-tertiary: #141414; - /* Very dark grey - Hover states */ - --bg-elevated: #1A1A1A; - /* Elevated surfaces (modals, dropdowns) */ + /* Background Colors / Màu nền */ + --bg-primary: #000000; + /* Pure black - Main background */ + --bg-secondary: #0A0A0A; + /* Almost black - Card/Panel background */ + --bg-tertiary: #141414; + /* Very dark grey - Hover states */ + --bg-elevated: #1A1A1A; + /* Elevated surfaces (modals, dropdowns) */ - /* Text Colors (WCAG Compliant) / Màu chữ (tuân thủ WCAG) */ - --text-primary: #FFFFFF; - /* Pure white - Primary text */ - --text-secondary: #B0B0B0; - /* Lighter grey - Secondary text (Improved contrast) */ - --text-tertiary: #808080; - /* Mid grey - Tertiary/disabled text (Improved contrast) */ - --text-muted: #505050; - /* Dark grey - Muted elements */ - --text-inverse: #000000; - /* Black - Text on light/white backgrounds */ + /* Text Colors (WCAG Compliant) / Màu chữ (tuân thủ WCAG) */ + --text-primary: #FFFFFF; + /* Pure white - Primary text */ + --text-secondary: #B0B0B0; + /* Lighter grey - Secondary text (Improved contrast) */ + --text-tertiary: #808080; + /* Mid grey - Tertiary/disabled text (Improved contrast) */ + --text-muted: #505050; + /* Dark grey - Muted elements */ + --text-inverse: #000000; + /* Black - Text on light/white backgrounds */ - /* Brand/Accent Colors / Màu thương hiệu/Accent */ - --accent-primary: #FFFFFF; - /* White - Primary actions (High contrast) */ - --accent-secondary: #333333; - /* Dark grey - Secondary actions */ - --accent-success: #10B981; - /* Green - Success states */ - --accent-warning: #F59E0B; - /* Amber - Warnings */ - --accent-error: #EF4444; - /* Red - Errors */ - --accent-info: #06B6D4; - /* Cyan - Info */ + /* Brand/Accent Colors / Màu thương hiệu/Accent */ + --accent-primary: #FFFFFF; + /* White - Primary actions (High contrast) */ + --accent-secondary: #333333; + /* Dark grey - Secondary actions */ + --accent-success: #10B981; + /* Green - Success states */ + --accent-warning: #F59E0B; + /* Amber - Warnings */ + --accent-error: #EF4444; + /* Red - Errors */ + --accent-info: #06B6D4; + /* Cyan - Info */ - /* Chat Specific Colors / Màu riêng cho Chat */ - --chat-user-bubble: #1A1A1A; - /* Dark grey - User message */ - --chat-ai-bubble: transparent; - /* Transparent - AI message (Minimal) */ - --chat-user-text: #FFFFFF; - /* White text */ - --chat-ai-text: #E5E5E5; - /* Off-white text */ - --chat-timestamp: #555555; - /* Dark grey timestamp */ - --chat-divider: #222222; - /* Divider between messages */ + /* Chat Specific Colors / Màu riêng cho Chat */ + --chat-user-bubble: #1A1A1A; + /* Dark grey - User message */ + --chat-ai-bubble: transparent; + /* Transparent - AI message (Minimal) */ + --chat-user-text: #FFFFFF; + /* White text */ + --chat-ai-text: #E5E5E5; + /* Off-white text */ + --chat-timestamp: #555555; + /* Dark grey timestamp */ + --chat-divider: #222222; + /* Divider between messages */ - /* Border Colors / Màu viền */ - --border-primary: #222222; - /* Subtle borders */ - --border-secondary: #333333; - /* Hover borders */ - --border-focus: #FFFFFF; - /* Focus state - White */ + /* Border Colors / Màu viền */ + --border-primary: #222222; + /* Subtle borders */ + --border-secondary: #333333; + /* Hover borders */ + --border-focus: #FFFFFF; + /* Focus state - White */ - /* ============================================ + /* ============================================ EN: Brand Colors (Primary Identity) VI: Màu thương hiệu (Nhận diện chính) ============================================ */ - /* Primary Brand Color - Main brand identity (Blue - Tech & Trust) */ - --brand-primary: #3B82F6; - --brand-primary-light: #60A5FA; - --brand-primary-dark: #2563EB; - --brand-primary-contrast: #FFFFFF; + /* Primary Brand Color - Main brand identity (Blue - Tech & Trust) */ + --brand-primary: #3B82F6; + --brand-primary-light: #60A5FA; + --brand-primary-dark: #2563EB; + --brand-primary-contrast: #FFFFFF; - /* Secondary Brand Color - Supporting color (Purple - Innovation) */ - --brand-secondary: #8B5CF6; - --brand-secondary-light: #A78BFA; - --brand-secondary-dark: #7C3AED; + /* Secondary Brand Color - Supporting color (Purple - Innovation) */ + --brand-secondary: #8B5CF6; + --brand-secondary-light: #A78BFA; + --brand-secondary-dark: #7C3AED; - /* Accent Color - Call-to-action (Cyan - Energy) */ - --brand-accent: #06B6D4; - --brand-accent-light: #22D3EE; - --brand-accent-dark: #0891B2; + /* Accent Color - Call-to-action (Cyan - Energy) */ + --brand-accent: #06B6D4; + --brand-accent-light: #22D3EE; + --brand-accent-dark: #0891B2; - /* Brand Gradients - For backgrounds and special elements */ - --brand-gradient-primary: linear-gradient(135deg, var(--brand-primary) 0%, var(--brand-secondary) 100%); - --brand-gradient-accent: linear-gradient(135deg, var(--brand-accent) 0%, var(--brand-primary) 100%); - --brand-gradient-vertical: linear-gradient(180deg, var(--brand-primary) 0%, var(--brand-secondary) 100%); + /* Brand Gradients - For backgrounds and special elements */ + --brand-gradient-primary: linear-gradient(135deg, var(--brand-primary) 0%, var(--brand-secondary) 100%); + --brand-gradient-accent: linear-gradient(135deg, var(--brand-accent) 0%, var(--brand-primary) 100%); + --brand-gradient-vertical: linear-gradient(180deg, var(--brand-primary) 0%, var(--brand-secondary) 100%); - /* ============================================ + /* ============================================ EN: Glassmorphism Effects VI: Hiệu ứng Glassmorphism ============================================ */ - --glass-bg: rgba(255, 255, 255, 0.05); - --glass-bg-hover: rgba(255, 255, 255, 0.1); - --glass-border: rgba(255, 255, 255, 0.08); - --glass-blur: 24px; + --glass-bg: rgba(255, 255, 255, 0.05); + --glass-bg-hover: rgba(255, 255, 255, 0.1); + --glass-border: rgba(255, 255, 255, 0.08); + --glass-blur: 24px; - /* ============================================ + /* ============================================ EN: Extended Shadows & Effects VI: Shadows & Effects mở rộng ============================================ */ - --shadow-brand: 0 20px 50px rgba(59, 130, 246, 0.15); - --shadow-brand-lg: 0 30px 70px rgba(59, 130, 246, 0.25); - --shadow-colored: 0 10px 40px rgba(59, 130, 246, 0.3); + --shadow-brand: 0 20px 50px rgba(59, 130, 246, 0.15); + --shadow-brand-lg: 0 30px 70px rgba(59, 130, 246, 0.25); + --shadow-colored: 0 10px 40px rgba(59, 130, 246, 0.3); - /* ============================================ + /* ============================================ EN: Light Mode Colors (Secondary Theme) VI: Màu sắc cho Light Mode (Theme phụ) ============================================ */ - --bg-primary-light: #FFFFFF; - --bg-secondary-light: #F8FAFC; - --bg-tertiary-light: #F1F5F9; - --text-primary-light: #0F172A; - --text-secondary-light: #475569; - --border-primary-light: #E2E8F0; + --bg-primary-light: #FFFFFF; + --bg-secondary-light: #FBFBFD; + /* Apple Gray */ + --bg-tertiary-light: #F5F5F7; + --text-primary-light: #1D1D1F; + /* Apple Black */ + --text-secondary-light: #86868B; + /* Apple Gray Text */ + --border-primary-light: #D2D2D7; - /* ============================================ + /* ============================================ EN: Typography VI: Kiểu chữ ============================================ */ - /* Font Stack / Bộ font */ - --font-sans: "Inter", -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, "Helvetica Neue", Arial, sans-serif; - --font-mono: "JetBrains Mono", "SF Mono", Consolas, "Liberation Mono", Menlo, monospace; + /* Font Stack / Bộ font */ + --font-sans: var(--font-inter), -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, "Helvetica Neue", Arial, sans-serif; + --font-mono: "JetBrains Mono", "SF Mono", Consolas, "Liberation Mono", Menlo, monospace; - /* Display Font - For hero titles (48px+) */ - --font-display: "Inter Display", -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, sans-serif; + /* Display Font - For hero titles (48px+) */ + --font-display: var(--font-inter), -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, sans-serif; - /* Heading Font - For section headings (24-36px) */ - --font-heading: "Inter", -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, sans-serif; + /* Heading Font - For section headings (24-36px) */ + --font-heading: var(--font-inter), -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, sans-serif; - /* Type Scale / Kích thước chữ */ - --text-6xl: 3.75rem; - /* 60px - Hero titles */ - --text-5xl: 3rem; - /* 48px - Page titles */ - --text-4xl: 2.25rem; - /* 36px - Section headers */ - --text-3xl: 1.875rem; - /* 30px - Card headers */ - --text-2xl: 1.5rem; - /* 24px - Large body */ - --text-xl: 1.25rem; - /* 20px - Emphasized text */ - --text-lg: 1.125rem; - /* 18px - Large body */ - --text-base: 1rem; - /* 16px - Default body */ - --text-sm: 0.875rem; - /* 14px - Small text */ - --text-xs: 0.75rem; - /* 12px - Captions */ + /* Type Scale / Kích thước chữ */ + --text-6xl: 3.75rem; + /* 60px - Hero titles */ + --text-5xl: 3rem; + /* 48px - Page titles */ + --text-4xl: 2.25rem; + /* 36px - Section headers */ + --text-3xl: 1.875rem; + /* 30px - Card headers */ + --text-2xl: 1.5rem; + /* 24px - Large body */ + --text-xl: 1.25rem; + /* 20px - Emphasized text */ + --text-lg: 1.125rem; + /* 18px - Large body */ + --text-base: 1rem; + /* 16px - Default body */ + --text-sm: 0.875rem; + /* 14px - Small text */ + --text-xs: 0.75rem; + /* 12px - Captions */ - /* Line Heights / Chiều cao dòng */ - --leading-none: 1; - --leading-tight: 1.1; - --leading-snug: 1.2; - --leading-normal: 1.5; - --leading-relaxed: 1.625; - --leading-loose: 2; + /* Line Heights / Chiều cao dòng */ + --leading-none: 1; + --leading-tight: 1.1; + --leading-snug: 1.2; + --leading-normal: 1.5; + --leading-relaxed: 1.625; + --leading-loose: 2; - /* Font Weights / Độ đậm chữ */ - --font-light: 300; - /* Light text */ - --font-normal: 400; - /* Body text */ - --font-medium: 500; - /* Emphasized */ - --font-semibold: 600; - /* Headings */ - --font-bold: 700; - /* Strong emphasis */ + /* Font Weights / Độ đậm chữ */ + --font-thin: 100; + --font-extralight: 200; + --font-light: 300; + /* Light text */ + --font-normal: 400; + /* Body text */ + --font-medium: 500; + /* Emphasized */ + --font-semibold: 600; + /* Headings */ + --font-bold: 700; + /* Strong emphasis */ - /* ============================================ + /* ============================================ EN: Spacing & Layout VI: Khoảng cách & Bố cục ============================================ */ - /* Base Unit: 4px (0.25rem) / Đơn vị cơ sở: 4px (0.25rem) */ - --space-0: 0; - --space-1: 0.25rem; - /* 4px */ - --space-2: 0.5rem; - /* 8px */ - --space-3: 0.75rem; - /* 12px */ - --space-4: 1rem; - /* 16px */ - --space-5: 1.25rem; - /* 20px */ - --space-6: 1.5rem; - /* 24px */ - --space-8: 2rem; - /* 32px */ - --space-10: 2.5rem; - /* 40px */ - --space-12: 3rem; - /* 48px */ - --space-16: 4rem; - /* 64px */ - --space-20: 5rem; - /* 80px */ + /* Base Unit: 4px (0.25rem) / Đơn vị cơ sở: 4px (0.25rem) */ + --space-0: 0; + --space-1: 0.25rem; + /* 4px */ + --space-2: 0.5rem; + /* 8px */ + --space-3: 0.75rem; + /* 12px */ + --space-4: 1rem; + /* 16px */ + --space-5: 1.25rem; + /* 20px */ + --space-6: 1.5rem; + /* 24px */ + --space-8: 2rem; + /* 32px */ + --space-10: 2.5rem; + /* 40px */ + --space-12: 3rem; + /* 48px */ + --space-16: 4rem; + /* 64px */ + --space-20: 5rem; + /* 80px */ - /* Container Widths / Chiều rộng container */ - --container-sm: 640px; - /* Small devices */ - --container-md: 768px; - /* Medium devices */ - --container-lg: 1024px; - /* Large devices */ - --container-xl: 1280px; - /* Extra large */ - --container-2xl: 1536px; - /* 2X large */ - --chat-max-width: 800px; - /* Max width for chat messages */ - --sidebar-width: 280px; - /* Conversation history sidebar */ + /* Container Widths / Chiều rộng container */ + --container-sm: 640px; + /* Small devices */ + --container-md: 768px; + /* Medium devices */ + --container-lg: 1024px; + /* Large devices */ + --container-xl: 1280px; + /* Extra large */ + --container-2xl: 1536px; + /* 2X large */ + --chat-max-width: 800px; + /* Max width for chat messages */ + --sidebar-width: 280px; + /* Conversation history sidebar */ - /* Border Radius / Bo góc */ - --radius-sm: 2px; - /* Small elements - sharp */ - --radius-md: 4px; - /* Buttons, inputs - sharp */ - --radius-lg: 8px; - /* Cards - minimal roundness */ - --radius-xl: 12px; - /* Large cards */ - --radius-2xl: 16px; - /* Modals */ - --radius-full: 9999px; - /* Full round - Avatars, pills */ + /* Border Radius / Bo góc */ + --radius-sm: 2px; + /* Small elements - sharp */ + --radius-md: 4px; + /* Buttons, inputs - sharp */ + --radius-lg: 8px; + /* Cards - minimal roundness */ + --radius-xl: 12px; + /* Large cards */ + --radius-2xl: 16px; + /* Modals */ + --radius-full: 9999px; + /* Full round - Avatars, pills */ - /* ============================================ + /* ============================================ EN: Shadows (Dark Mode Optimized) VI: Đổ bóng (Tối ưu cho Dark Mode) ============================================ */ - --shadow-sm: 0 1px 2px rgba(0, 0, 0, 0.8); - --shadow-md: 0 4px 6px rgba(0, 0, 0, 0.8); - --shadow-lg: 0 10px 15px rgba(0, 0, 0, 0.9); - --shadow-xl: 0 20px 25px rgba(0, 0, 0, 0.95); - --shadow-glow: 0 0 20px rgba(255, 255, 255, 0.1); - /* White glow for focus */ + --shadow-sm: 0 1px 2px rgba(0, 0, 0, 0.8); + --shadow-md: 0 4px 6px rgba(0, 0, 0, 0.8); + --shadow-lg: 0 10px 15px rgba(0, 0, 0, 0.9); + --shadow-xl: 0 20px 25px rgba(0, 0, 0, 0.95); + --shadow-glow: 0 0 20px rgba(255, 255, 255, 0.1); + /* White glow for focus */ - /* ============================================ + /* ============================================ EN: Grid System & Breakpoints VI: Hệ thống lưới & Điểm ngắt ============================================ */ - --screen-sm: 640px; - /* Mobile landscape */ - --screen-md: 768px; - /* Tablet */ - --screen-lg: 1024px; - /* Desktop */ - --screen-xl: 1280px; - /* Large desktop */ - --screen-2xl: 1536px; - /* Extra large desktop */ + --screen-sm: 640px; + /* Mobile landscape */ + --screen-md: 768px; + /* Tablet */ + --screen-lg: 1024px; + /* Desktop */ + --screen-xl: 1280px; + /* Large desktop */ + --screen-2xl: 1536px; + /* Extra large desktop */ - /* ============================================ + /* ============================================ EN: Animation & Transitions VI: Animation & Chuyển tiếp ============================================ */ - /* Timing Functions / Hàm thời gian */ - --ease-in: cubic-bezier(0.4, 0, 1, 1); - --ease-out: cubic-bezier(0, 0, 0.2, 1); - --ease-in-out: cubic-bezier(0.4, 0, 0.2, 1); - --ease-spring: cubic-bezier(0.34, 1.56, 0.64, 1); + /* Timing Functions / Hàm thời gian */ + --ease-in: cubic-bezier(0.4, 0, 1, 1); + --ease-out: cubic-bezier(0, 0, 0.2, 1); + --ease-in-out: cubic-bezier(0.4, 0, 0.2, 1); + --ease-spring: cubic-bezier(0.34, 1.56, 0.64, 1); - /* Duration / Thời lượng */ - --duration-fast: 150ms; - /* Hover effects */ - --duration-normal: 250ms; - /* Default transitions */ - --duration-slow: 350ms; - /* Complex animations */ - --duration-slower: 500ms; - /* Page transitions */ + /* Duration / Thời lượng */ + --duration-fast: 150ms; + /* Hover effects */ + --duration-normal: 250ms; + /* Default transitions */ + --duration-slow: 350ms; + /* Complex animations */ + --duration-slower: 500ms; + /* Page transitions */ - /* ============================================ + /* ============================================ EN: Advanced Motion Tokens VI: Token chuyển động nâng cao ============================================ */ - /* Motion Ease Functions - For micro-interactions */ - --motion-ease-bounce: cubic-bezier(0.68, -0.55, 0.265, 1.55); - --motion-ease-elastic: cubic-bezier(0.68, -0.6, 0.32, 1.6); + /* Motion Ease Functions - For micro-interactions */ + --motion-ease-bounce: cubic-bezier(0.68, -0.55, 0.265, 1.55); + --motion-ease-elastic: cubic-bezier(0.68, -0.6, 0.32, 1.6); - /* Hover Scale - For interactive elements */ - --hover-scale-sm: 1.02; - --hover-scale-md: 1.05; - --hover-scale-lg: 1.1; + /* Hover Scale - For interactive elements */ + --hover-scale-sm: 1.02; + --hover-scale-md: 1.05; + --hover-scale-lg: 1.1; - /* Active Scale - For pressed states */ - --active-scale: 0.98; + /* Active Scale - For pressed states */ + --active-scale: 0.98; } /* ============================================ @@ -320,14 +325,14 @@ VI: Ghi đè theme cho Light Mode ============================================ */ @media (prefers-color-scheme: light) { - :root { - --bg-primary: var(--bg-primary-light); - --bg-secondary: var(--bg-secondary-light); - --bg-tertiary: var(--bg-tertiary-light); - --text-primary: var(--text-primary-light); - --text-secondary: var(--text-secondary-light); - --border-primary: var(--border-primary-light); - } + :root { + --bg-primary: var(--bg-primary-light); + --bg-secondary: var(--bg-secondary-light); + --bg-tertiary: var(--bg-tertiary-light); + --text-primary: var(--text-primary-light); + --text-secondary: var(--text-secondary-light); + --border-primary: var(--border-primary-light); + } } /* ============================================ @@ -336,16 +341,16 @@ ============================================ */ [data-theme="dark"], .dark { - --bg-primary: #000000; - --bg-secondary: #0A0A0A; - --bg-tertiary: #141414; - --bg-elevated: #1A1A1A; - --text-primary: #FFFFFF; - --text-secondary: #B0B0B0; - --text-tertiary: #808080; - --text-muted: #505050; - --border-primary: #222222; - --border-secondary: #333333; + --bg-primary: #000000; + --bg-secondary: #0A0A0A; + --bg-tertiary: #141414; + --bg-elevated: #1A1A1A; + --text-primary: #FFFFFF; + --text-secondary: #B0B0B0; + --text-tertiary: #808080; + --text-muted: #505050; + --border-primary: #222222; + --border-secondary: #333333; } /* ============================================ @@ -354,10 +359,10 @@ ============================================ */ [data-theme="light"], .light { - --bg-primary: var(--bg-primary-light); - --bg-secondary: var(--bg-secondary-light); - --bg-tertiary: var(--bg-tertiary-light); - --text-primary: var(--text-primary-light); - --text-secondary: var(--text-secondary-light); - --border-primary: var(--border-primary-light); + --bg-primary: var(--bg-primary-light); + --bg-secondary: var(--bg-secondary-light); + --bg-tertiary: var(--bg-tertiary-light); + --text-primary: var(--text-primary-light); + --text-secondary: var(--text-secondary-light); + --border-primary: var(--border-primary-light); } \ No newline at end of file diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 2cf394e3..abd21561 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -144,6 +144,15 @@ importers: '@radix-ui/react-dropdown-menu': specifier: ^2.0.6 version: 2.1.16(@types/react-dom@18.3.7)(@types/react@18.3.27)(react-dom@18.3.1)(react@18.3.1) + '@radix-ui/react-popover': + specifier: ^1.1.15 + version: 1.1.15(@types/react-dom@18.3.7)(@types/react@18.3.27)(react-dom@18.3.1)(react@18.3.1) + '@radix-ui/react-switch': + specifier: ^1.2.6 + version: 1.2.6(@types/react-dom@18.3.7)(@types/react@18.3.27)(react-dom@18.3.1)(react@18.3.1) + '@radix-ui/react-tooltip': + specifier: ^1.2.8 + version: 1.2.8(@types/react-dom@18.3.7)(@types/react@18.3.27)(react-dom@18.3.1)(react@18.3.1) '@tanstack/react-query': specifier: ^5.17.0 version: 5.90.16(react@18.3.1) @@ -4078,6 +4087,40 @@ packages: react-remove-scroll: 2.7.2(@types/react@18.3.27)(react@18.3.1) dev: false + /@radix-ui/react-popover@1.1.15(@types/react-dom@18.3.7)(@types/react@18.3.27)(react-dom@18.3.1)(react@18.3.1): + resolution: {integrity: sha512-kr0X2+6Yy/vJzLYJUPCZEc8SfQcf+1COFoAqauJm74umQhta9M7lNJHP7QQS3vkvcGLQUbWpMzwrXYwrYztHKA==} + peerDependencies: + '@types/react': '*' + '@types/react-dom': '*' + react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + react-dom: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + peerDependenciesMeta: + '@types/react': + optional: true + '@types/react-dom': + optional: true + dependencies: + '@radix-ui/primitive': 1.1.3 + '@radix-ui/react-compose-refs': 1.1.2(@types/react@18.3.27)(react@18.3.1) + '@radix-ui/react-context': 1.1.2(@types/react@18.3.27)(react@18.3.1) + '@radix-ui/react-dismissable-layer': 1.1.11(@types/react-dom@18.3.7)(@types/react@18.3.27)(react-dom@18.3.1)(react@18.3.1) + '@radix-ui/react-focus-guards': 1.1.3(@types/react@18.3.27)(react@18.3.1) + '@radix-ui/react-focus-scope': 1.1.7(@types/react-dom@18.3.7)(@types/react@18.3.27)(react-dom@18.3.1)(react@18.3.1) + '@radix-ui/react-id': 1.1.1(@types/react@18.3.27)(react@18.3.1) + '@radix-ui/react-popper': 1.2.8(@types/react-dom@18.3.7)(@types/react@18.3.27)(react-dom@18.3.1)(react@18.3.1) + '@radix-ui/react-portal': 1.1.9(@types/react-dom@18.3.7)(@types/react@18.3.27)(react-dom@18.3.1)(react@18.3.1) + '@radix-ui/react-presence': 1.1.5(@types/react-dom@18.3.7)(@types/react@18.3.27)(react-dom@18.3.1)(react@18.3.1) + '@radix-ui/react-primitive': 2.1.3(@types/react-dom@18.3.7)(@types/react@18.3.27)(react-dom@18.3.1)(react@18.3.1) + '@radix-ui/react-slot': 1.2.3(@types/react@18.3.27)(react@18.3.1) + '@radix-ui/react-use-controllable-state': 1.2.2(@types/react@18.3.27)(react@18.3.1) + '@types/react': 18.3.27 + '@types/react-dom': 18.3.7(@types/react@18.3.27) + aria-hidden: 1.2.6 + react: 18.3.1 + react-dom: 18.3.1(react@18.3.1) + react-remove-scroll: 2.7.2(@types/react@18.3.27)(react@18.3.1) + dev: false + /@radix-ui/react-popper@1.2.8(@types/react-dom@18.3.7)(@types/react@18.3.27)(react-dom@18.3.1)(react@18.3.1): resolution: {integrity: sha512-0NJQ4LFFUuWkE7Oxf0htBKS6zLkkjBH+hM1uk7Ng705ReR8m/uelduy1DBo0PyBXPKVnBA6YBlU94MBGXrSBCw==} peerDependencies: @@ -4245,6 +4288,32 @@ packages: react: 18.3.1 dev: false + /@radix-ui/react-switch@1.2.6(@types/react-dom@18.3.7)(@types/react@18.3.27)(react-dom@18.3.1)(react@18.3.1): + resolution: {integrity: sha512-bByzr1+ep1zk4VubeEVViV592vu2lHE2BZY5OnzehZqOOgogN80+mNtCqPkhn2gklJqOpxWgPoYTSnhBCqpOXQ==} + peerDependencies: + '@types/react': '*' + '@types/react-dom': '*' + react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + react-dom: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + peerDependenciesMeta: + '@types/react': + optional: true + '@types/react-dom': + optional: true + dependencies: + '@radix-ui/primitive': 1.1.3 + '@radix-ui/react-compose-refs': 1.1.2(@types/react@18.3.27)(react@18.3.1) + '@radix-ui/react-context': 1.1.2(@types/react@18.3.27)(react@18.3.1) + '@radix-ui/react-primitive': 2.1.3(@types/react-dom@18.3.7)(@types/react@18.3.27)(react-dom@18.3.1)(react@18.3.1) + '@radix-ui/react-use-controllable-state': 1.2.2(@types/react@18.3.27)(react@18.3.1) + '@radix-ui/react-use-previous': 1.1.1(@types/react@18.3.27)(react@18.3.1) + '@radix-ui/react-use-size': 1.1.1(@types/react@18.3.27)(react@18.3.1) + '@types/react': 18.3.27 + '@types/react-dom': 18.3.7(@types/react@18.3.27) + react: 18.3.1 + react-dom: 18.3.1(react@18.3.1) + dev: false + /@radix-ui/react-tabs@1.1.13(@types/react-dom@18.3.7)(@types/react@18.3.27)(react-dom@18.3.1)(react@18.3.1): resolution: {integrity: sha512-7xdcatg7/U+7+Udyoj2zodtI9H/IIopqo+YOIcZOq1nJwXWBZ9p8xiu5llXlekDbZkca79a/fozEYQXIA4sW6A==} peerDependencies: @@ -4272,6 +4341,37 @@ packages: react-dom: 18.3.1(react@18.3.1) dev: false + /@radix-ui/react-tooltip@1.2.8(@types/react-dom@18.3.7)(@types/react@18.3.27)(react-dom@18.3.1)(react@18.3.1): + resolution: {integrity: sha512-tY7sVt1yL9ozIxvmbtN5qtmH2krXcBCfjEiCgKGLqunJHvgvZG2Pcl2oQ3kbcZARb1BGEHdkLzcYGO8ynVlieg==} + peerDependencies: + '@types/react': '*' + '@types/react-dom': '*' + react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + react-dom: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + peerDependenciesMeta: + '@types/react': + optional: true + '@types/react-dom': + optional: true + dependencies: + '@radix-ui/primitive': 1.1.3 + '@radix-ui/react-compose-refs': 1.1.2(@types/react@18.3.27)(react@18.3.1) + '@radix-ui/react-context': 1.1.2(@types/react@18.3.27)(react@18.3.1) + '@radix-ui/react-dismissable-layer': 1.1.11(@types/react-dom@18.3.7)(@types/react@18.3.27)(react-dom@18.3.1)(react@18.3.1) + '@radix-ui/react-id': 1.1.1(@types/react@18.3.27)(react@18.3.1) + '@radix-ui/react-popper': 1.2.8(@types/react-dom@18.3.7)(@types/react@18.3.27)(react-dom@18.3.1)(react@18.3.1) + '@radix-ui/react-portal': 1.1.9(@types/react-dom@18.3.7)(@types/react@18.3.27)(react-dom@18.3.1)(react@18.3.1) + '@radix-ui/react-presence': 1.1.5(@types/react-dom@18.3.7)(@types/react@18.3.27)(react-dom@18.3.1)(react@18.3.1) + '@radix-ui/react-primitive': 2.1.3(@types/react-dom@18.3.7)(@types/react@18.3.27)(react-dom@18.3.1)(react@18.3.1) + '@radix-ui/react-slot': 1.2.3(@types/react@18.3.27)(react@18.3.1) + '@radix-ui/react-use-controllable-state': 1.2.2(@types/react@18.3.27)(react@18.3.1) + '@radix-ui/react-visually-hidden': 1.2.3(@types/react-dom@18.3.7)(@types/react@18.3.27)(react-dom@18.3.1)(react@18.3.1) + '@types/react': 18.3.27 + '@types/react-dom': 18.3.7(@types/react@18.3.27) + react: 18.3.1 + react-dom: 18.3.1(react@18.3.1) + dev: false + /@radix-ui/react-use-callback-ref@1.1.1(@types/react@18.3.27)(react@18.3.1): resolution: {integrity: sha512-FkBMwD+qbGQeMu1cOHnuGB6x4yzPjho8ap5WtbEJ26umhgqVXbhekKUQO+hZEL1vU92a3wHwdp0HAcqAUF5iDg==} peerDependencies: @@ -4355,6 +4455,19 @@ packages: react: 18.3.1 dev: false + /@radix-ui/react-use-previous@1.1.1(@types/react@18.3.27)(react@18.3.1): + resolution: {integrity: sha512-2dHfToCj/pzca2Ck724OZ5L0EVrr3eHRNsG/b3xQJLA2hZpVCS99bLAX+hm1IHXDEnzU6by5z/5MIY794/a8NQ==} + peerDependencies: + '@types/react': '*' + react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + peerDependenciesMeta: + '@types/react': + optional: true + dependencies: + '@types/react': 18.3.27 + react: 18.3.1 + dev: false + /@radix-ui/react-use-rect@1.1.1(@types/react@18.3.27)(react@18.3.1): resolution: {integrity: sha512-QTYuDesS0VtuHNNvMh+CjlKJ4LJickCMUAqjlE3+j8w+RlRpwyX3apEQKGFzbZGdo7XNG1tXa+bQqIE7HIXT2w==} peerDependencies: @@ -4383,6 +4496,26 @@ packages: react: 18.3.1 dev: false + /@radix-ui/react-visually-hidden@1.2.3(@types/react-dom@18.3.7)(@types/react@18.3.27)(react-dom@18.3.1)(react@18.3.1): + resolution: {integrity: sha512-pzJq12tEaaIhqjbzpCuv/OypJY/BPavOofm+dbab+MHLajy277+1lLm6JFcGgF5eskJ6mquGirhXY2GD/8u8Ug==} + peerDependencies: + '@types/react': '*' + '@types/react-dom': '*' + react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + react-dom: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + peerDependenciesMeta: + '@types/react': + optional: true + '@types/react-dom': + optional: true + dependencies: + '@radix-ui/react-primitive': 2.1.3(@types/react-dom@18.3.7)(@types/react@18.3.27)(react-dom@18.3.1)(react@18.3.1) + '@types/react': 18.3.27 + '@types/react-dom': 18.3.7(@types/react@18.3.27) + react: 18.3.1 + react-dom: 18.3.1(react@18.3.1) + dev: false + /@radix-ui/rect@1.1.1: resolution: {integrity: sha512-HPwpGIzkl28mWyZqG52jiqDJ12waP11Pa1lGoiyUkIEuMLBP0oeK/C89esbXrxsky5we7dfd8U58nm0SgAWpVw==} dev: false