@nextclaw/ui 0.2.4 → 0.2.5

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (78) hide show
  1. package/CHANGELOG.md +6 -0
  2. package/dist/assets/index-BV3Gyu8h.js +225 -0
  3. package/dist/assets/index-iSLahgqA.css +1 -0
  4. package/dist/index.html +2 -2
  5. package/dist/logos/aihubmix.png +0 -0
  6. package/dist/logos/anthropic.svg +1 -0
  7. package/dist/logos/dashscope.png +0 -0
  8. package/dist/logos/deepseek.png +0 -0
  9. package/dist/logos/dingtalk.svg +1 -0
  10. package/dist/logos/discord.svg +1 -0
  11. package/dist/logos/email.svg +1 -0
  12. package/dist/logos/feishu.svg +12 -0
  13. package/dist/logos/gemini.svg +1 -0
  14. package/dist/logos/groq.svg +1 -0
  15. package/dist/logos/minimax.svg +1 -0
  16. package/dist/logos/mochat.svg +6 -0
  17. package/dist/logos/moonshot.png +0 -0
  18. package/dist/logos/openai.svg +1 -0
  19. package/dist/logos/openrouter.svg +1 -0
  20. package/dist/logos/qq.svg +1 -0
  21. package/dist/logos/slack.svg +1 -0
  22. package/dist/logos/telegram.svg +1 -0
  23. package/dist/logos/vllm.svg +1 -0
  24. package/dist/logos/whatsapp.svg +1 -0
  25. package/dist/logos/zhipu.svg +15 -0
  26. package/package.json +1 -1
  27. package/public/logos/aihubmix.png +0 -0
  28. package/public/logos/anthropic.svg +1 -0
  29. package/public/logos/dashscope.png +0 -0
  30. package/public/logos/deepseek.png +0 -0
  31. package/public/logos/dingtalk.svg +1 -0
  32. package/public/logos/discord.svg +1 -0
  33. package/public/logos/email.svg +1 -0
  34. package/public/logos/feishu.svg +12 -0
  35. package/public/logos/gemini.svg +1 -0
  36. package/public/logos/groq.svg +1 -0
  37. package/public/logos/minimax.svg +1 -0
  38. package/public/logos/mochat.svg +6 -0
  39. package/public/logos/moonshot.png +0 -0
  40. package/public/logos/openai.svg +1 -0
  41. package/public/logos/openrouter.svg +1 -0
  42. package/public/logos/qq.svg +1 -0
  43. package/public/logos/slack.svg +1 -0
  44. package/public/logos/telegram.svg +1 -0
  45. package/public/logos/vllm.svg +1 -0
  46. package/public/logos/whatsapp.svg +1 -0
  47. package/public/logos/zhipu.svg +15 -0
  48. package/src/App.tsx +0 -3
  49. package/src/api/config.ts +0 -19
  50. package/src/api/types.ts +0 -8
  51. package/src/components/common/LogoBadge.tsx +35 -0
  52. package/src/components/common/StatusBadge.tsx +4 -4
  53. package/src/components/config/ChannelForm.tsx +16 -18
  54. package/src/components/config/ChannelsList.tsx +87 -37
  55. package/src/components/config/ModelConfig.tsx +25 -25
  56. package/src/components/config/ProviderForm.tsx +9 -11
  57. package/src/components/config/ProvidersList.tsx +90 -38
  58. package/src/components/layout/Header.tsx +7 -7
  59. package/src/components/layout/Sidebar.tsx +10 -23
  60. package/src/components/ui/HighlightCard.tsx +29 -29
  61. package/src/components/ui/button.tsx +13 -8
  62. package/src/components/ui/card.tsx +8 -7
  63. package/src/components/ui/dialog.tsx +8 -8
  64. package/src/components/ui/input.tsx +1 -1
  65. package/src/components/ui/label.tsx +1 -1
  66. package/src/components/ui/switch.tsx +3 -3
  67. package/src/components/ui/tabs-custom.tsx +6 -6
  68. package/src/components/ui/tabs.tsx +7 -6
  69. package/src/hooks/useConfig.ts +2 -29
  70. package/src/index.css +103 -56
  71. package/src/lib/i18n.ts +3 -6
  72. package/src/lib/logos.ts +42 -0
  73. package/src/stores/ui.store.ts +1 -1
  74. package/src/styles/design-system.css +248 -0
  75. package/tailwind.config.js +118 -10
  76. package/dist/assets/index-C4OKhpdC.css +0 -1
  77. package/dist/assets/index-C8nOCIVG.js +0 -240
  78. package/src/components/config/UiConfig.tsx +0 -189
@@ -1,22 +1,22 @@
1
1
  import { useConfig, useConfigMeta } from '@/hooks/useConfig';
2
2
  import { Button } from '@/components/ui/button';
3
- import { Skeleton } from '@/components/ui/skeleton';
4
- import { KeyRound, Lock, Check, Plus, MoreHorizontal } from 'lucide-react';
3
+ import { KeyRound, Check, Settings } from 'lucide-react';
5
4
  import { useState } from 'react';
6
5
  import { ProviderForm } from './ProviderForm';
7
6
  import { useUiStore } from '@/stores/ui.store';
8
7
  import { cn } from '@/lib/utils';
9
8
  import { Tabs } from '@/components/ui/tabs-custom';
10
- import { HighlightCard } from '@/components/ui/HighlightCard';
9
+ import { LogoBadge } from '@/components/common/LogoBadge';
10
+ import { getProviderLogo } from '@/lib/logos';
11
11
 
12
12
  export function ProvidersList() {
13
13
  const { data: config } = useConfig();
14
14
  const { data: meta } = useConfigMeta();
15
15
  const { openProviderModal } = useUiStore();
16
- const [activeTab, setActiveTab] = useState('featured');
16
+ const [activeTab, setActiveTab] = useState('installed');
17
17
 
18
18
  if (!config || !meta) {
19
- return <div className="p-8">Loading...</div>; // Skeleton optimization can follow
19
+ return <div className="p-8">Loading...</div>;
20
20
  }
21
21
 
22
22
  const tabs = [
@@ -24,66 +24,118 @@ export function ProvidersList() {
24
24
  { id: 'all', label: 'All Providers' }
25
25
  ];
26
26
 
27
+ const filteredProviders = activeTab === 'installed'
28
+ ? meta.providers.filter((p) => config.providers[p.name]?.apiKeySet)
29
+ : meta.providers;
30
+
27
31
  return (
28
32
  <div className="animate-fade-in pb-20">
29
33
  <div className="flex items-center justify-between mb-8">
30
- <h2 className="text-2xl font-bold text-[hsl(30,15%,10%)]">AI Providers</h2>
34
+ <h2 className="text-2xl font-bold text-gray-900">AI Providers</h2>
31
35
  </div>
32
36
 
33
- {/* Tabs */}
34
- <Tabs tabs={tabs} activeTab={activeTab === 'featured' ? 'all' : activeTab} onChange={setActiveTab} />
37
+ <Tabs tabs={tabs} activeTab={activeTab} onChange={setActiveTab} />
35
38
 
36
- {/* Provider List Row-Style */}
37
- <div className="space-y-1">
38
- {(activeTab === 'installed'
39
- ? meta.providers.filter((p) => config.providers[p.name]?.apiKeySet)
40
- : meta.providers
41
- ).map((provider) => {
39
+ {/* Provider Cards Grid */}
40
+ <div className="grid grid-cols-1 sm:grid-cols-2 lg:grid-cols-3 xl:grid-cols-4 gap-4">
41
+ {filteredProviders.map((provider) => {
42
42
  const providerConfig = config.providers[provider.name];
43
43
  const hasConfig = providerConfig?.apiKeySet;
44
44
 
45
45
  return (
46
46
  <div
47
47
  key={provider.name}
48
- className="group flex items-center gap-5 p-3 rounded-2xl hover:bg-[hsl(40,10%,96%)] transition-all cursor-pointer border border-transparent hover:border-[hsl(40,10%,94%)]"
48
+ className={cn(
49
+ 'group relative flex flex-col p-5 rounded-2xl border transition-all duration-base cursor-pointer',
50
+ 'hover:shadow-card-hover hover:-translate-y-0.5',
51
+ hasConfig
52
+ ? 'bg-white border-gray-200 hover:border-gray-300'
53
+ : 'bg-gray-50 border-gray-200 hover:border-gray-300 hover:bg-white'
54
+ )}
49
55
  onClick={() => openProviderModal(provider.name)}
50
56
  >
51
- {/* Logo/Icon */}
52
- <div className="h-10 w-10 flex items-center justify-center bg-transparent border border-[hsl(40,10%,92%)] rounded-xl group-hover:scale-105 transition-transform overflow-hidden">
53
- <span className="text-xl font-bold uppercase text-[hsl(30,15%,10%)]">{provider.name[0]}</span>
57
+ {/* Header with Logo and Status */}
58
+ <div className="flex items-start justify-between mb-4">
59
+ <LogoBadge
60
+ name={provider.name}
61
+ src={getProviderLogo(provider.name)}
62
+ className={cn(
63
+ 'h-12 w-12 rounded-xl border transition-all',
64
+ hasConfig
65
+ ? 'bg-white border-primary'
66
+ : 'bg-white border-gray-200 group-hover:border-gray-300'
67
+ )}
68
+ imgClassName="h-7 w-7"
69
+ fallback={(
70
+ <span className={cn(
71
+ 'text-lg font-bold uppercase',
72
+ hasConfig ? 'text-gray-900' : 'text-gray-500'
73
+ )}>
74
+ {provider.name[0]}
75
+ </span>
76
+ )}
77
+ />
78
+
79
+ {/* Status Badge */}
80
+ {hasConfig ? (
81
+ <div className="flex items-center gap-1.5 px-2.5 py-1 rounded-full bg-emerald-50 text-emerald-600">
82
+ <Check className="h-3.5 w-3.5" />
83
+ <span className="text-[11px] font-bold">Ready</span>
84
+ </div>
85
+ ) : (
86
+ <div className="flex items-center gap-1.5 px-2.5 py-1 rounded-full bg-gray-100 text-gray-500">
87
+ <Settings className="h-3.5 w-3.5" />
88
+ <span className="text-[11px] font-bold">Setup</span>
89
+ </div>
90
+ )}
54
91
  </div>
55
92
 
56
- {/* Info */}
57
- <div className="flex-1 min-w-0">
58
- <h3 className="text-[14px] font-bold text-[hsl(30,15%,10%)] truncate">
93
+ {/* Provider Info */}
94
+ <div className="flex-1">
95
+ <h3 className="text-[15px] font-bold text-gray-900 mb-1">
59
96
  {provider.displayName || provider.name}
60
97
  </h3>
61
- <p className="text-[12px] text-[hsl(30,8%,55%)] truncate leading-tight">
62
- {provider.name === 'openai' ? 'TypeScript authentication framework integration guide' : 'Configure AI services for your agents'}
98
+ <p className="text-[12px] text-gray-500 leading-relaxed line-clamp-2">
99
+ {provider.name === 'openai'
100
+ ? 'Leading AI models including GPT-4 and GPT-3.5'
101
+ : 'Configure AI services for your agents'}
63
102
  </p>
64
103
  </div>
65
104
 
66
- {/* Status/Actions */}
67
- <div className="flex items-center gap-4">
68
- {hasConfig ? (
69
- <div className="flex items-center gap-1.5 text-emerald-600">
70
- <Check className="h-4 w-4" />
71
- <span className="text-[11px] font-bold">Ready</span>
72
- </div>
73
- ) : (
74
- <button className="h-8 w-8 flex items-center justify-center text-[hsl(30,8%,45%)] hover:text-[hsl(30,15%,10%)] group-hover:opacity-100 opacity-0 transition-opacity">
75
- <Plus className="h-4 w-4" />
76
- </button>
77
- )}
78
- <button className="h-8 w-8 flex items-center justify-center text-[hsl(30,8%,45%)]">
79
- <MoreHorizontal className="h-4 w-4" />
80
- </button>
105
+ {/* Footer with Action */}
106
+ <div className="mt-4 pt-4 border-t border-gray-100">
107
+ <Button
108
+ variant={hasConfig ? 'ghost' : 'default'}
109
+ size="sm"
110
+ className="w-full rounded-xl text-xs font-semibold h-9"
111
+ onClick={(e) => {
112
+ e.stopPropagation();
113
+ openProviderModal(provider.name);
114
+ }}
115
+ >
116
+ {hasConfig ? 'Configure' : 'Add Provider'}
117
+ </Button>
81
118
  </div>
82
119
  </div>
83
120
  );
84
121
  })}
85
122
  </div>
86
123
 
124
+ {/* Empty State */}
125
+ {filteredProviders.length === 0 && (
126
+ <div className="flex flex-col items-center justify-center py-16 text-center">
127
+ <div className="h-16 w-16 flex items-center justify-center rounded-2xl bg-gray-100 mb-4">
128
+ <KeyRound className="h-8 w-8 text-gray-400" />
129
+ </div>
130
+ <h3 className="text-[15px] font-bold text-gray-900 mb-2">
131
+ No providers configured
132
+ </h3>
133
+ <p className="text-[13px] text-gray-500 max-w-sm">
134
+ Add an AI provider to start using the platform. Click on any provider to configure it.
135
+ </p>
136
+ </div>
137
+ )}
138
+
87
139
  <ProviderForm />
88
140
  </div>
89
141
  );
@@ -7,27 +7,27 @@ interface HeaderProps {
7
7
 
8
8
  export function Header({ title, description }: HeaderProps) {
9
9
  return (
10
- <header className="h-16 bg-white/80 backdrop-blur-md sticky top-0 z-10 border-b border-[hsl(40,20%,90%)] flex items-center justify-between px-6 transition-all duration-300">
10
+ <header className="h-16 bg-white/80 backdrop-blur-md sticky top-0 z-10 border-b border-gray-200 flex items-center justify-between px-6 transition-all duration-base">
11
11
  <div className="flex items-center gap-4">
12
12
  {title && (
13
13
  <div>
14
- <h2 className="text-base font-semibold text-[hsl(30,20%,12%)]">{title}</h2>
14
+ <h2 className="text-base font-semibold text-gray-900">{title}</h2>
15
15
  {description && (
16
- <p className="text-xs text-[hsl(30,8%,45%)]">{description}</p>
16
+ <p className="text-xs text-gray-500">{description}</p>
17
17
  )}
18
18
  </div>
19
19
  )}
20
20
  </div>
21
21
 
22
22
  <div className="flex items-center gap-3">
23
- <button className="h-9 w-9 rounded-lg bg-[hsl(40,20%,96%)] flex items-center justify-center text-[hsl(30,8%,45%)] hover:bg-[hsl(40,20%,94%)] hover:text-[hsl(30,20%,12%)] transition-colors">
23
+ <button className="h-9 w-9 rounded-lg bg-gray-100 flex items-center justify-center text-gray-500 hover:bg-gray-200 hover:text-gray-700 transition-colors duration-fast">
24
24
  <Search className="h-4 w-4" />
25
25
  </button>
26
- <button className="h-9 w-9 rounded-lg bg-[hsl(40,20%,96%)] flex items-center justify-center text-[hsl(30,8%,45%)] hover:bg-[hsl(40,20%,94%)] hover:text-[hsl(30,20%,12%)] transition-colors relative">
26
+ <button className="h-9 w-9 rounded-lg bg-gray-100 flex items-center justify-center text-gray-500 hover:bg-gray-200 hover:text-gray-700 transition-colors duration-fast relative">
27
27
  <Bell className="h-4 w-4" />
28
- <span className="absolute top-1.5 right-1.5 h-2 w-2 rounded-full bg-orange-500" />
28
+ <span className="absolute top-1.5 right-1.5 h-2 w-2 rounded-full bg-primary" />
29
29
  </button>
30
- <div className="h-9 w-9 rounded-lg bg-gradient-to-br from-orange-400 to-amber-500 flex items-center justify-center">
30
+ <div className="h-9 w-9 rounded-lg bg-gradient-to-br from-primary to-primary-600 flex items-center justify-center">
31
31
  <span className="text-xs font-semibold text-white">N</span>
32
32
  </div>
33
33
  </div>
@@ -1,37 +1,22 @@
1
1
  import { useUiStore } from '@/stores/ui.store';
2
2
  import { cn } from '@/lib/utils';
3
- import {
4
- Cpu,
5
- MessageSquare,
6
- Sparkles,
7
- ChevronRight,
8
- Settings
9
- } from 'lucide-react';
3
+ import { Cpu, MessageSquare, Sparkles } from 'lucide-react';
10
4
 
11
5
  const navItems = [
12
6
  {
13
7
  id: 'model' as const,
14
8
  label: 'Models',
15
9
  icon: Cpu,
16
- color: 'text-[hsl(30,15%,10%)]'
17
10
  },
18
11
  {
19
12
  id: 'providers' as const,
20
13
  label: 'Providers',
21
14
  icon: Sparkles,
22
- color: 'text-[hsl(30,15%,10%)]'
23
15
  },
24
16
  {
25
17
  id: 'channels' as const,
26
18
  label: 'Channels',
27
19
  icon: MessageSquare,
28
- color: 'text-[hsl(30,15%,10%)]'
29
- },
30
- {
31
- id: 'ui' as const,
32
- label: 'Appearance',
33
- icon: Settings,
34
- color: 'text-[hsl(30,15%,10%)]'
35
20
  }
36
21
  ];
37
22
 
@@ -43,10 +28,10 @@ export function Sidebar() {
43
28
  {/* Logo Area */}
44
29
  <div className="px-3 mb-8">
45
30
  <div className="flex items-center gap-2 group cursor-pointer">
46
- <div className="h-7 w-7 rounded-lg overflow-hidden flex items-center justify-center transition-transform group-hover:scale-110">
31
+ <div className="h-7 w-7 rounded-lg overflow-hidden flex items-center justify-center transition-transform duration-fast group-hover:scale-110">
47
32
  <img src="/logo.svg" alt="NextClaw Logo" className="h-full w-full object-contain" />
48
33
  </div>
49
- <h1 className="text-lg font-bold text-[hsl(30,15%,10%)] tracking-tight">nextclaw</h1>
34
+ <h1 className="text-lg font-bold text-gray-900 tracking-tight">nextclaw</h1>
50
35
  </div>
51
36
  </div>
52
37
 
@@ -62,20 +47,22 @@ export function Sidebar() {
62
47
  <button
63
48
  onClick={() => setActiveTab(item.id)}
64
49
  className={cn(
65
- 'group w-full flex items-center gap-3 px-3 py-2 rounded-lg text-[13px] font-medium transition-all duration-200',
50
+ 'group w-full flex items-center gap-3 px-3 py-2 rounded-lg text-sm font-medium transition-all duration-fast',
66
51
  isActive
67
- ? 'bg-[hsl(40,10%,92%)] text-[hsl(30,15%,10%)]'
68
- : 'text-[hsl(30,8%,45%)] hover:bg-[hsl(40,10%,94%)] hover:text-[hsl(30,15%,10%)]'
52
+ ? 'bg-primary-100 text-primary-700'
53
+ : 'text-gray-600 hover:bg-gray-100 hover:text-gray-900'
69
54
  )}
70
55
  >
71
- <Icon className={cn('h-4 w-4 transition-transform group-hover:scale-110', isActive ? 'text-[hsl(30,15%,10%)]' : 'text-[hsl(30,8%,45%)]')} />
56
+ <Icon className={cn(
57
+ 'h-4 w-4 transition-transform duration-fast group-hover:scale-110',
58
+ isActive ? 'text-primary' : 'text-gray-500'
59
+ )} />
72
60
  <span className="flex-1 text-left">{item.label}</span>
73
61
  </button>
74
62
  </li>
75
63
  );
76
64
  })}
77
65
  </ul>
78
-
79
66
  </nav>
80
67
  </aside>
81
68
  );
@@ -3,38 +3,38 @@ import { cn } from '@/lib/utils';
3
3
  import { ArrowRight } from 'lucide-react';
4
4
 
5
5
  interface HighlightCardProps {
6
- category: string;
7
- title: string;
8
- description: string;
9
- image: string;
10
- gradient: string;
11
- className?: string;
6
+ category: string;
7
+ title: string;
8
+ description: string;
9
+ image: string;
10
+ gradient: string;
11
+ className?: string;
12
12
  }
13
13
 
14
14
  export function HighlightCard({ category, title, description, image, gradient, className }: HighlightCardProps) {
15
- return (
16
- <div className={cn(
17
- 'group relative overflow-hidden rounded-[1.5rem] bg-white border border-[hsl(40,10%,94%)] flex h-[180px] transition-all duration-300 hover:shadow-premium cursor-pointer',
18
- className
19
- )}>
20
- <div className="flex-1 p-6 flex flex-col">
21
- <span className="text-[10px] font-bold text-[hsl(30,8%,65%)] uppercase tracking-widest mb-2">{category}</span>
22
- <h3 className="text-xl font-bold text-[hsl(30,15%,10%)] leading-tight mb-2 group-hover:text-amber-600 transition-colors">{title}</h3>
23
- <p className="text-[13px] text-[hsl(30,8%,55%)] line-clamp-2 leading-relaxed max-w-[200px]">{description}</p>
15
+ return (
16
+ <div className={cn(
17
+ 'group relative overflow-hidden rounded-xl bg-white border border-gray-200 flex h-[180px] transition-all duration-base hover:shadow-card-hover hover:border-gray-300 cursor-pointer',
18
+ className
19
+ )}>
20
+ <div className="flex-1 p-6 flex flex-col">
21
+ <span className="text-[10px] font-semibold text-gray-400 uppercase tracking-widest mb-2">{category}</span>
22
+ <h3 className="text-lg font-semibold text-gray-900 leading-tight mb-2 group-hover:text-primary transition-colors duration-fast">{title}</h3>
23
+ <p className="text-sm text-gray-500 line-clamp-2 leading-relaxed max-w-[200px]">{description}</p>
24
24
 
25
- <div className="mt-auto flex items-center gap-1 text-[11px] font-bold text-[hsl(30,15%,10%)] opacity-0 group-hover:opacity-100 transition-opacity">
26
- Learn more <ArrowRight className="h-3 w-3" />
27
- </div>
28
- </div>
29
-
30
- <div className={cn('w-[160px] relative overflow-hidden', gradient)}>
31
- <img
32
- src={image}
33
- alt={title}
34
- className="w-full h-full object-cover mix-blend-multiply opacity-90 group-hover:scale-110 transition-transform duration-500"
35
- />
36
- <div className="absolute inset-0 bg-gradient-to-l from-transparent to-white/10" />
37
- </div>
25
+ <div className="mt-auto flex items-center gap-1 text-xs font-medium text-primary opacity-0 group-hover:opacity-100 transition-opacity duration-fast">
26
+ Learn more <ArrowRight className="h-3 w-3" />
38
27
  </div>
39
- );
28
+ </div>
29
+
30
+ <div className={cn('w-[160px] relative overflow-hidden', gradient)}>
31
+ <img
32
+ src={image}
33
+ alt={title}
34
+ className="w-full h-full object-cover mix-blend-multiply opacity-90 group-hover:scale-110 transition-transform duration-slow"
35
+ />
36
+ <div className="absolute inset-0 bg-gradient-to-l from-transparent to-white/10" />
37
+ </div>
38
+ </div>
39
+ );
40
40
  }
@@ -3,21 +3,26 @@ import { cva, type VariantProps } from 'class-variance-authority';
3
3
  import { cn } from '@/lib/utils';
4
4
 
5
5
  const buttonVariants = cva(
6
- 'inline-flex items-center justify-center whitespace-nowrap rounded-md text-sm font-medium ring-offset-background transition-all duration-200 active:scale-[0.98] focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 disabled:pointer-events-none disabled:opacity-50',
6
+ 'inline-flex items-center justify-center whitespace-nowrap rounded-lg text-sm font-medium ring-offset-background transition-all duration-fast focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 disabled:pointer-events-none disabled:opacity-50',
7
7
  {
8
8
  variants: {
9
9
  variant: {
10
- default: 'bg-primary text-primary-foreground hover:bg-primary/90',
10
+ default: 'bg-primary text-primary-foreground hover:bg-primary-600 active:bg-primary-700 shadow-sm',
11
11
  destructive: 'bg-destructive text-destructive-foreground hover:bg-destructive/90',
12
- outline: 'border border-input bg-background hover:bg-accent hover:text-accent-foreground',
13
- secondary: 'bg-secondary text-secondary-foreground hover:bg-secondary/80',
14
- ghost: 'hover:bg-accent hover:text-accent-foreground',
15
- link: 'text-primary underline-offset-4 hover:underline'
12
+ outline: 'border border-input bg-background hover:bg-secondary hover:text-secondary-foreground',
13
+ secondary: 'bg-secondary text-secondary-foreground hover:bg-gray-200',
14
+ ghost: 'hover:bg-secondary hover:text-secondary-foreground',
15
+ link: 'text-primary underline-offset-4 hover:underline',
16
+ // New variants matching the design
17
+ primary: 'bg-primary text-primary-foreground hover:bg-primary-600 active:bg-primary-700 shadow-sm',
18
+ subtle: 'bg-secondary text-secondary-foreground hover:bg-gray-200',
19
+ 'primary-outline': 'border-2 border-primary text-primary hover:bg-primary hover:text-primary-foreground'
16
20
  },
17
21
  size: {
18
22
  default: 'h-10 px-4 py-2',
19
- sm: 'h-9 rounded-md px-3',
20
- lg: 'h-11 rounded-md px-8',
23
+ sm: 'h-8 rounded-md px-3 text-xs',
24
+ lg: 'h-12 rounded-xl px-6 text-base',
25
+ xl: 'h-14 rounded-xl px-8 text-base',
21
26
  icon: 'h-10 w-10'
22
27
  }
23
28
  },
@@ -3,12 +3,13 @@ import { cn } from '@/lib/utils';
3
3
 
4
4
  const Card = React.forwardRef<
5
5
  HTMLDivElement,
6
- React.HTMLAttributes<HTMLDivElement>
7
- >(({ className, ...props }, ref) => (
6
+ React.HTMLAttributes<HTMLDivElement> & { hover?: boolean }
7
+ >(({ className, hover = true, ...props }, ref) => (
8
8
  <div
9
9
  ref={ref}
10
10
  className={cn(
11
- 'rounded-2xl border bg-card text-card-foreground shadow-sm transition-all duration-300 hover:shadow-premium',
11
+ 'rounded-xl border border-gray-200 bg-white text-card-foreground shadow-card transition-all duration-base',
12
+ hover && 'hover:shadow-card-hover hover:border-gray-300',
12
13
  className
13
14
  )}
14
15
  {...props}
@@ -22,7 +23,7 @@ const CardHeader = React.forwardRef<
22
23
  >(({ className, ...props }, ref) => (
23
24
  <div
24
25
  ref={ref}
25
- className={cn('flex flex-col space-y-1.5 p-6', className)}
26
+ className={cn('flex flex-col space-y-2 p-6', className)}
26
27
  {...props}
27
28
  />
28
29
  ));
@@ -35,7 +36,7 @@ const CardTitle = React.forwardRef<
35
36
  <h3
36
37
  ref={ref}
37
38
  className={cn(
38
- 'text-2xl font-semibold leading-none tracking-tight',
39
+ 'text-lg font-semibold leading-tight tracking-tight text-gray-900',
39
40
  className
40
41
  )}
41
42
  {...props}
@@ -49,7 +50,7 @@ const CardDescription = React.forwardRef<
49
50
  >(({ className, ...props }, ref) => (
50
51
  <p
51
52
  ref={ref}
52
- className={cn('text-sm text-muted-foreground', className)}
53
+ className={cn('text-sm text-gray-500 leading-relaxed', className)}
53
54
  {...props}
54
55
  />
55
56
  ));
@@ -69,7 +70,7 @@ const CardFooter = React.forwardRef<
69
70
  >(({ className, ...props }, ref) => (
70
71
  <div
71
72
  ref={ref}
72
- className={cn('flex items-center p-6 pt-0', className)}
73
+ className={cn('flex items-center justify-between p-6 pt-0', className)}
73
74
  {...props}
74
75
  />
75
76
  ));
@@ -19,7 +19,7 @@ const DialogOverlay = React.forwardRef<
19
19
  <DialogPrimitive.Overlay
20
20
  ref={ref}
21
21
  className={cn(
22
- "fixed inset-0 z-50 bg-black/50 data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0",
22
+ "fixed inset-0 z-50 bg-black/40 backdrop-blur-sm data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0",
23
23
  className
24
24
  )}
25
25
  {...props}
@@ -36,14 +36,14 @@ const DialogContent = React.forwardRef<
36
36
  <DialogPrimitive.Content
37
37
  ref={ref}
38
38
  className={cn(
39
- "fixed left-[50%] top-[50%] z-50 grid w-full max-w-lg translate-x-[-50%] translate-y-[-50%] gap-4 border border-[hsl(40,20%,90%)] bg-white p-6 shadow-2xl duration-200 data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0 data-[state=closed]:zoom-out-95 data-[state=open]:zoom-in-95 data-[state=closed]:slide-out-to-left-1/2 data-[state=closed]:slide-out-to-top-[48%] data-[state=open]:slide-in-from-left-1/2 data-[state=open]:slide-in-from-top-[48%] rounded-2xl",
39
+ "fixed left-[50%] top-[50%] z-50 grid w-full max-w-lg translate-x-[-50%] translate-y-[-50%] gap-4 border border-gray-200 bg-white p-6 shadow-xl duration-base data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0 data-[state=closed]:zoom-out-95 data-[state=open]:zoom-in-95 data-[state=closed]:slide-out-to-left-1/2 data-[state=closed]:slide-out-to-top-[48%] data-[state=open]:slide-in-from-left-1/2 data-[state=open]:slide-in-from-top-[48%] rounded-xl",
40
40
  className
41
41
  )}
42
42
  {...props}
43
43
  >
44
44
  {children}
45
- <DialogPrimitive.Close className="absolute right-4 top-4 rounded-lg opacity-70 ring-offset-white transition-opacity hover:opacity-100 focus:outline-none focus:ring-2 focus:ring-[hsl(25,95%,53%)] focus:ring-offset-2 disabled:pointer-events-none data-[state=open]:bg-[hsl(40,20%,96%)] data-[state=open]:text-[hsl(30,8%,45%)]">
46
- <X className="h-4 w-4 text-[hsl(30,8%,45%)]" />
45
+ <DialogPrimitive.Close className="absolute right-4 top-4 rounded-lg p-1 opacity-70 ring-offset-white transition-all duration-fast hover:opacity-100 hover:bg-gray-100 focus:outline-none focus-visible:ring-2 focus-visible:ring-primary focus-visible:ring-offset-2 disabled:pointer-events-none">
46
+ <X className="h-4 w-4 text-gray-500" />
47
47
  <span className="sr-only">Close</span>
48
48
  </DialogPrimitive.Close>
49
49
  </DialogPrimitive.Content>
@@ -57,7 +57,7 @@ const DialogHeader = ({
57
57
  }: React.HTMLAttributes<HTMLDivElement>) => (
58
58
  <div
59
59
  className={cn(
60
- "flex flex-col space-y-1.5 text-center sm:text-left",
60
+ "flex flex-col space-y-2 text-center sm:text-left",
61
61
  className
62
62
  )}
63
63
  {...props}
@@ -71,7 +71,7 @@ const DialogFooter = ({
71
71
  }: React.HTMLAttributes<HTMLDivElement>) => (
72
72
  <div
73
73
  className={cn(
74
- "flex flex-col-reverse sm:flex-row sm:justify-end sm:gap-2",
74
+ "flex flex-col-reverse sm:flex-row sm:justify-end sm:gap-3",
75
75
  className
76
76
  )}
77
77
  {...props}
@@ -86,7 +86,7 @@ const DialogTitle = React.forwardRef<
86
86
  <DialogPrimitive.Title
87
87
  ref={ref}
88
88
  className={cn(
89
- "text-lg font-semibold leading-none tracking-tight text-[hsl(30,20%,12%)]",
89
+ "text-lg font-semibold leading-tight tracking-tight text-gray-900",
90
90
  className
91
91
  )}
92
92
  {...props}
@@ -100,7 +100,7 @@ const DialogDescription = React.forwardRef<
100
100
  >(({ className, ...props }, ref) => (
101
101
  <DialogPrimitive.Description
102
102
  ref={ref}
103
- className={cn("text-sm text-[hsl(30,8%,45%)]", className)}
103
+ className={cn("text-sm text-gray-500 leading-relaxed", className)}
104
104
  {...props}
105
105
  />
106
106
  ))
@@ -9,7 +9,7 @@ const Input = React.forwardRef<HTMLInputElement, InputProps>(
9
9
  <input
10
10
  type={type}
11
11
  className={cn(
12
- 'flex h-10 w-full rounded-md border border-input bg-background px-3 py-2 text-sm ring-offset-background file:border-0 file:bg-transparent file:text-sm file:font-medium placeholder:text-muted-foreground focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 disabled:cursor-not-allowed disabled:opacity-50',
12
+ 'flex h-10 w-full rounded-lg border border-gray-200 bg-white px-3 py-2 text-sm file:border-0 file:bg-transparent file:text-sm file:font-medium placeholder:text-gray-400 focus:outline-none disabled:cursor-not-allowed disabled:opacity-50',
13
13
  className
14
14
  )}
15
15
  ref={ref}
@@ -8,7 +8,7 @@ const Label = React.forwardRef<HTMLLabelElement, LabelProps>(
8
8
  <label
9
9
  ref={ref}
10
10
  className={cn(
11
- 'text-sm font-medium leading-none peer-disabled:cursor-not-allowed peer-disabled:opacity-70',
11
+ 'text-sm font-medium leading-none text-gray-700 peer-disabled:cursor-not-allowed peer-disabled:opacity-70',
12
12
  className
13
13
  )}
14
14
  {...props}
@@ -15,8 +15,8 @@ const Switch = React.forwardRef<HTMLButtonElement, SwitchProps>(
15
15
  aria-checked={checked}
16
16
  ref={ref}
17
17
  className={cn(
18
- 'peer inline-flex h-6 w-11 shrink-0 cursor-pointer items-center rounded-full border-2 border-transparent transition-colors focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 focus-visible:ring-offset-background disabled:cursor-not-allowed disabled:opacity-50',
19
- checked ? 'bg-primary' : 'bg-input',
18
+ 'peer inline-flex h-6 w-11 shrink-0 cursor-pointer items-center rounded-full border-2 border-transparent transition-colors duration-fast focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-primary focus-visible:ring-offset-2 focus-visible:ring-offset-white disabled:cursor-not-allowed disabled:opacity-50',
19
+ checked ? 'bg-primary' : 'bg-gray-200 hover:bg-gray-300',
20
20
  className
21
21
  )}
22
22
  onClick={() => onCheckedChange?.(!checked)}
@@ -24,7 +24,7 @@ const Switch = React.forwardRef<HTMLButtonElement, SwitchProps>(
24
24
  >
25
25
  <span
26
26
  className={cn(
27
- 'pointer-events-none block h-5 w-5 rounded-full bg-background shadow-lg ring-0 transition-transform',
27
+ 'pointer-events-none block h-5 w-5 rounded-full bg-white shadow-md ring-0 transition-transform duration-fast',
28
28
  checked ? 'translate-x-5' : 'translate-x-0'
29
29
  )}
30
30
  />
@@ -16,7 +16,7 @@ interface TabsProps {
16
16
 
17
17
  export function Tabs({ tabs, activeTab, onChange, className }: TabsProps) {
18
18
  return (
19
- <div className={cn('flex items-center gap-8 border-b border-[hsl(40,10%,94%)] mb-8', className)}>
19
+ <div className={cn('flex items-center gap-8 border-b border-gray-200 mb-8', className)}>
20
20
  {tabs.map((tab) => {
21
21
  const isActive = activeTab === tab.id;
22
22
  return (
@@ -24,18 +24,18 @@ export function Tabs({ tabs, activeTab, onChange, className }: TabsProps) {
24
24
  key={tab.id}
25
25
  onClick={() => onChange(tab.id)}
26
26
  className={cn(
27
- 'relative pb-4 text-[15px] font-semibold transition-all duration-200 flex items-center gap-2',
27
+ 'relative pb-4 text-[15px] font-semibold transition-all duration-fast flex items-center gap-2',
28
28
  isActive
29
- ? 'text-[hsl(30,15%,10%)]'
30
- : 'text-[hsl(30,8%,55%)] hover:text-[hsl(30,15%,10%)]'
29
+ ? 'text-primary'
30
+ : 'text-gray-500 hover:text-gray-700'
31
31
  )}
32
32
  >
33
33
  {tab.label}
34
34
  {tab.count !== undefined && (
35
- <span className="text-[11px] font-medium text-[hsl(30,8%,65%)]">{tab.count.toLocaleString()}</span>
35
+ <span className="text-[11px] font-medium text-gray-400">{tab.count.toLocaleString()}</span>
36
36
  )}
37
37
  {isActive && (
38
- <div className="absolute bottom-0 left-0 right-0 h-0.5 bg-[hsl(30,15%,10%)] animate-in fade-in slide-in-from-left-2 duration-300" />
38
+ <div className="absolute bottom-0 left-0 right-0 h-0.5 bg-primary animate-in fade-in slide-in-from-left-2 duration-300" />
39
39
  )}
40
40
  </button>
41
41
  );
@@ -42,7 +42,7 @@ export function TabsList({ children, className }: TabsListProps) {
42
42
  return (
43
43
  <div
44
44
  className={cn(
45
- 'inline-flex h-10 items-center justify-center rounded-md bg-muted p-1 text-muted-foreground',
45
+ 'inline-flex h-10 items-center justify-center rounded-lg bg-gray-100 p-1 text-gray-600',
46
46
  className
47
47
  )}
48
48
  >
@@ -62,10 +62,10 @@ export function TabsTrigger({ value, children, className }: TabsTriggerProps) {
62
62
  type="button"
63
63
  onClick={() => context.onValueChange(value)}
64
64
  className={cn(
65
- 'inline-flex items-center justify-center whitespace-nowrap rounded-sm px-3 py-1.5 text-sm font-medium ring-offset-background transition-all focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 disabled:pointer-events-none disabled:opacity-50',
65
+ 'inline-flex items-center justify-center whitespace-nowrap rounded-md px-3 py-1.5 text-sm font-medium ring-offset-white transition-all duration-fast focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-primary focus-visible:ring-offset-2 disabled:pointer-events-none disabled:opacity-50',
66
66
  isActive
67
- ? 'bg-background text-foreground shadow-sm'
68
- : 'hover:bg-background/50',
67
+ ? 'bg-white text-gray-900 shadow-sm'
68
+ : 'hover:bg-white/50 hover:text-gray-700',
69
69
  className
70
70
  )}
71
71
  >
@@ -78,10 +78,11 @@ export function TabsContent({ value, children, className }: TabsContentProps) {
78
78
  const context = React.useContext(TabsContext);
79
79
  if (!context) throw new Error('TabsContent must be used within Tabs');
80
80
 
81
- if (context.value !== value) return null;
81
+ const isActive = context.value === value;
82
+ if (!isActive) return null;
82
83
 
83
84
  return (
84
- <div className={cn('mt-2 ring-offset-background focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2', className)}>
85
+ <div className={cn('mt-2 animate-fade-in', className)}>
85
86
  {children}
86
87
  </div>
87
88
  );