@nextclaw/ui 0.2.4 → 0.3.0

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 +12 -0
  2. package/dist/assets/index-BIesvTqn.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 +5 -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 +46 -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 +7 -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
@@ -27,6 +27,7 @@ export function ProviderForm() {
27
27
  const [apiKey, setApiKey] = useState('');
28
28
  const [apiBase, setApiBase] = useState('');
29
29
  const [extraHeaders, setExtraHeaders] = useState<Record<string, string> | null>(null);
30
+ const [wireApi, setWireApi] = useState<'auto' | 'chat' | 'responses'>('auto');
30
31
 
31
32
  const providerName = providerModal.provider;
32
33
  const providerSpec = meta?.providers.find((p) => p.name === providerName);
@@ -37,6 +38,9 @@ export function ProviderForm() {
37
38
  setApiBase(providerConfig.apiBase || providerSpec?.defaultApiBase || '');
38
39
  setExtraHeaders(providerConfig.extraHeaders || null);
39
40
  setApiKey(''); // Always start with empty for security
41
+ const nextWireApi =
42
+ providerConfig.wireApi || providerSpec?.defaultWireApi || 'auto';
43
+ setWireApi(nextWireApi as 'auto' | 'chat' | 'responses');
40
44
  }
41
45
  }, [providerConfig, providerSpec]);
42
46
 
@@ -58,6 +62,14 @@ export function ProviderForm() {
58
62
  payload.extraHeaders = extraHeaders;
59
63
  }
60
64
 
65
+ if (providerSpec?.supportsWireApi) {
66
+ const currentWireApi =
67
+ providerConfig?.wireApi || providerSpec.defaultWireApi || 'auto';
68
+ if (wireApi !== currentWireApi) {
69
+ payload.wireApi = wireApi;
70
+ }
71
+ }
72
+
61
73
  if (!providerName) return;
62
74
 
63
75
  updateProvider.mutate(
@@ -71,7 +83,7 @@ export function ProviderForm() {
71
83
  <DialogContent className="sm:max-w-[500px]">
72
84
  <DialogHeader>
73
85
  <div className="flex items-center gap-3">
74
- <div className="h-10 w-10 rounded-xl bg-gradient-to-br from-orange-400 to-amber-500 flex items-center justify-center">
86
+ <div className="h-10 w-10 rounded-xl bg-primary flex items-center justify-center">
75
87
  <KeyRound className="h-5 w-5 text-white" />
76
88
  </div>
77
89
  <div>
@@ -83,8 +95,8 @@ export function ProviderForm() {
83
95
 
84
96
  <form onSubmit={handleSubmit} className="space-y-5 pt-2">
85
97
  <div className="space-y-2.5">
86
- <Label htmlFor="apiKey" className="text-sm font-medium text-[hsl(30,20%,12%)] flex items-center gap-2">
87
- <KeyRound className="h-3.5 w-3.5 text-[hsl(30,8%,45%)]" />
98
+ <Label htmlFor="apiKey" className="text-sm font-medium text-gray-900 flex items-center gap-2">
99
+ <KeyRound className="h-3.5 w-3.5 text-gray-500" />
88
100
  {t('apiKey')}
89
101
  </Label>
90
102
  <MaskedInput
@@ -93,13 +105,13 @@ export function ProviderForm() {
93
105
  isSet={providerConfig?.apiKeySet}
94
106
  onChange={(e) => setApiKey(e.target.value)}
95
107
  placeholder={providerConfig?.apiKeySet ? t('apiKeySet') : 'Enter API Key'}
96
- className="rounded-xl border-[hsl(40,20%,90%)] bg-[hsl(40,20%,98%)] focus:bg-white"
108
+ className="rounded-xl"
97
109
  />
98
110
  </div>
99
111
 
100
112
  <div className="space-y-2.5">
101
- <Label htmlFor="apiBase" className="text-sm font-medium text-[hsl(30,20%,12%)] flex items-center gap-2">
102
- <Globe className="h-3.5 w-3.5 text-[hsl(30,8%,45%)]" />
113
+ <Label htmlFor="apiBase" className="text-sm font-medium text-gray-900 flex items-center gap-2">
114
+ <Globe className="h-3.5 w-3.5 text-gray-500" />
103
115
  {t('apiBase')}
104
116
  </Label>
105
117
  <Input
@@ -108,13 +120,38 @@ export function ProviderForm() {
108
120
  value={apiBase}
109
121
  onChange={(e) => setApiBase(e.target.value)}
110
122
  placeholder={providerSpec?.defaultApiBase || 'https://api.example.com'}
111
- className="rounded-xl border-[hsl(40,20%,90%)] bg-[hsl(40,20%,98%)] focus:bg-white"
123
+ className="rounded-xl"
112
124
  />
113
125
  </div>
114
126
 
127
+ {providerSpec?.supportsWireApi && (
128
+ <div className="space-y-2.5">
129
+ <Label htmlFor="wireApi" className="text-sm font-medium text-gray-900 flex items-center gap-2">
130
+ <Hash className="h-3.5 w-3.5 text-gray-500" />
131
+ {t('wireApi')}
132
+ </Label>
133
+ <select
134
+ id="wireApi"
135
+ value={wireApi}
136
+ onChange={(e) => setWireApi(e.target.value as 'auto' | 'chat' | 'responses')}
137
+ className="flex h-10 w-full rounded-lg border border-gray-200 bg-white px-3 py-2 text-sm focus:outline-none disabled:cursor-not-allowed disabled:opacity-50"
138
+ >
139
+ {(providerSpec.wireApiOptions || ['auto', 'chat', 'responses']).map((option) => (
140
+ <option key={option} value={option}>
141
+ {option === 'chat'
142
+ ? t('wireApiChat')
143
+ : option === 'responses'
144
+ ? t('wireApiResponses')
145
+ : t('wireApiAuto')}
146
+ </option>
147
+ ))}
148
+ </select>
149
+ </div>
150
+ )}
151
+
115
152
  <div className="space-y-2.5">
116
- <Label className="text-sm font-medium text-[hsl(30,20%,12%)] flex items-center gap-2">
117
- <Hash className="h-3.5 w-3.5 text-[hsl(30,8%,45%)]" />
153
+ <Label className="text-sm font-medium text-gray-900 flex items-center gap-2">
154
+ <Hash className="h-3.5 w-3.5 text-gray-500" />
118
155
  {t('extraHeaders')}
119
156
  </Label>
120
157
  <KeyValueEditor
@@ -128,14 +165,12 @@ export function ProviderForm() {
128
165
  type="button"
129
166
  variant="outline"
130
167
  onClick={closeProviderModal}
131
- className="rounded-xl border-[hsl(40,20%,90%)] bg-white hover:bg-[hsl(40,20%,96%)]"
132
168
  >
133
169
  {t('cancel')}
134
170
  </Button>
135
171
  <Button
136
172
  type="submit"
137
173
  disabled={updateProvider.isPending}
138
- className="rounded-xl bg-gradient-to-r from-orange-400 to-amber-500 hover:from-orange-500 hover:to-amber-600 text-white border-0"
139
174
  >
140
175
  {updateProvider.isPending ? 'Saving...' : t('save')}
141
176
  </Button>
@@ -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
  ))