@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.
- package/CHANGELOG.md +6 -0
- package/dist/assets/index-BV3Gyu8h.js +225 -0
- package/dist/assets/index-iSLahgqA.css +1 -0
- package/dist/index.html +2 -2
- package/dist/logos/aihubmix.png +0 -0
- package/dist/logos/anthropic.svg +1 -0
- package/dist/logos/dashscope.png +0 -0
- package/dist/logos/deepseek.png +0 -0
- package/dist/logos/dingtalk.svg +1 -0
- package/dist/logos/discord.svg +1 -0
- package/dist/logos/email.svg +1 -0
- package/dist/logos/feishu.svg +12 -0
- package/dist/logos/gemini.svg +1 -0
- package/dist/logos/groq.svg +1 -0
- package/dist/logos/minimax.svg +1 -0
- package/dist/logos/mochat.svg +6 -0
- package/dist/logos/moonshot.png +0 -0
- package/dist/logos/openai.svg +1 -0
- package/dist/logos/openrouter.svg +1 -0
- package/dist/logos/qq.svg +1 -0
- package/dist/logos/slack.svg +1 -0
- package/dist/logos/telegram.svg +1 -0
- package/dist/logos/vllm.svg +1 -0
- package/dist/logos/whatsapp.svg +1 -0
- package/dist/logos/zhipu.svg +15 -0
- package/package.json +1 -1
- package/public/logos/aihubmix.png +0 -0
- package/public/logos/anthropic.svg +1 -0
- package/public/logos/dashscope.png +0 -0
- package/public/logos/deepseek.png +0 -0
- package/public/logos/dingtalk.svg +1 -0
- package/public/logos/discord.svg +1 -0
- package/public/logos/email.svg +1 -0
- package/public/logos/feishu.svg +12 -0
- package/public/logos/gemini.svg +1 -0
- package/public/logos/groq.svg +1 -0
- package/public/logos/minimax.svg +1 -0
- package/public/logos/mochat.svg +6 -0
- package/public/logos/moonshot.png +0 -0
- package/public/logos/openai.svg +1 -0
- package/public/logos/openrouter.svg +1 -0
- package/public/logos/qq.svg +1 -0
- package/public/logos/slack.svg +1 -0
- package/public/logos/telegram.svg +1 -0
- package/public/logos/vllm.svg +1 -0
- package/public/logos/whatsapp.svg +1 -0
- package/public/logos/zhipu.svg +15 -0
- package/src/App.tsx +0 -3
- package/src/api/config.ts +0 -19
- package/src/api/types.ts +0 -8
- package/src/components/common/LogoBadge.tsx +35 -0
- package/src/components/common/StatusBadge.tsx +4 -4
- package/src/components/config/ChannelForm.tsx +16 -18
- package/src/components/config/ChannelsList.tsx +87 -37
- package/src/components/config/ModelConfig.tsx +25 -25
- package/src/components/config/ProviderForm.tsx +9 -11
- package/src/components/config/ProvidersList.tsx +90 -38
- package/src/components/layout/Header.tsx +7 -7
- package/src/components/layout/Sidebar.tsx +10 -23
- package/src/components/ui/HighlightCard.tsx +29 -29
- package/src/components/ui/button.tsx +13 -8
- package/src/components/ui/card.tsx +8 -7
- package/src/components/ui/dialog.tsx +8 -8
- package/src/components/ui/input.tsx +1 -1
- package/src/components/ui/label.tsx +1 -1
- package/src/components/ui/switch.tsx +3 -3
- package/src/components/ui/tabs-custom.tsx +6 -6
- package/src/components/ui/tabs.tsx +7 -6
- package/src/hooks/useConfig.ts +2 -29
- package/src/index.css +103 -56
- package/src/lib/i18n.ts +3 -6
- package/src/lib/logos.ts +42 -0
- package/src/stores/ui.store.ts +1 -1
- package/src/styles/design-system.css +248 -0
- package/tailwind.config.js +118 -10
- package/dist/assets/index-C4OKhpdC.css +0 -1
- package/dist/assets/index-C8nOCIVG.js +0 -240
- 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 {
|
|
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 {
|
|
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('
|
|
16
|
+
const [activeTab, setActiveTab] = useState('installed');
|
|
17
17
|
|
|
18
18
|
if (!config || !meta) {
|
|
19
|
-
return <div className="p-8">Loading...</div>;
|
|
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-
|
|
34
|
+
<h2 className="text-2xl font-bold text-gray-900">AI Providers</h2>
|
|
31
35
|
</div>
|
|
32
36
|
|
|
33
|
-
{
|
|
34
|
-
<Tabs tabs={tabs} activeTab={activeTab === 'featured' ? 'all' : activeTab} onChange={setActiveTab} />
|
|
37
|
+
<Tabs tabs={tabs} activeTab={activeTab} onChange={setActiveTab} />
|
|
35
38
|
|
|
36
|
-
{/* Provider
|
|
37
|
-
<div className="
|
|
38
|
-
{(
|
|
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=
|
|
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
|
|
52
|
-
<div className="
|
|
53
|
-
<
|
|
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
|
|
58
|
-
<h3 className="text-[
|
|
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-
|
|
62
|
-
{provider.name === 'openai'
|
|
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
|
-
{/*
|
|
67
|
-
<div className="
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
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-
|
|
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-
|
|
14
|
+
<h2 className="text-base font-semibold text-gray-900">{title}</h2>
|
|
15
15
|
{description && (
|
|
16
|
-
<p className="text-xs text-
|
|
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-
|
|
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-
|
|
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-
|
|
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-
|
|
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-
|
|
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-
|
|
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-
|
|
68
|
-
: 'text-
|
|
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(
|
|
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
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
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
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
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
|
-
|
|
26
|
-
|
|
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-
|
|
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
|
|
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-
|
|
13
|
-
secondary: 'bg-secondary text-secondary-foreground hover:bg-
|
|
14
|
-
ghost: 'hover:bg-
|
|
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-
|
|
20
|
-
lg: 'h-
|
|
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-
|
|
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-
|
|
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-
|
|
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-
|
|
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/
|
|
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-
|
|
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-
|
|
46
|
-
<X className="h-4 w-4 text-
|
|
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-
|
|
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-
|
|
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-
|
|
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-
|
|
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-
|
|
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-
|
|
19
|
-
checked ? 'bg-primary' : 'bg-
|
|
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-
|
|
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-
|
|
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-
|
|
27
|
+
'relative pb-4 text-[15px] font-semibold transition-all duration-fast flex items-center gap-2',
|
|
28
28
|
isActive
|
|
29
|
-
? 'text-
|
|
30
|
-
: 'text-
|
|
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-
|
|
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-
|
|
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-
|
|
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-
|
|
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-
|
|
68
|
-
: 'hover:bg-
|
|
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
|
-
|
|
81
|
+
const isActive = context.value === value;
|
|
82
|
+
if (!isActive) return null;
|
|
82
83
|
|
|
83
84
|
return (
|
|
84
|
-
<div className={cn('mt-2
|
|
85
|
+
<div className={cn('mt-2 animate-fade-in', className)}>
|
|
85
86
|
{children}
|
|
86
87
|
</div>
|
|
87
88
|
);
|