@nextclaw/ui 0.5.6 → 0.5.8
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 +12 -0
- package/dist/assets/index-BtwwwWcv.css +1 -0
- package/dist/assets/index-STUSj6p9.js +337 -0
- package/dist/index.html +2 -2
- package/package.json +1 -1
- package/src/components/common/StatusBadge.tsx +9 -13
- package/src/components/config/ChannelForm.tsx +10 -0
- package/src/components/config/ChannelsList.tsx +33 -59
- package/src/components/config/CronConfig.tsx +28 -30
- package/src/components/config/ModelConfig.tsx +1 -1
- package/src/components/config/ProvidersList.tsx +32 -58
- package/src/components/config/RuntimeConfig.tsx +1 -1
- package/src/components/config/SessionsConfig.tsx +1 -1
- package/src/components/layout/AppLayout.tsx +3 -3
- package/src/components/layout/Header.tsx +4 -19
- package/src/components/layout/Sidebar.tsx +13 -21
- package/src/components/marketplace/MarketplacePage.tsx +17 -17
- package/src/components/ui/action-link.tsx +27 -0
- package/src/components/ui/button.tsx +12 -13
- package/src/components/ui/card.tsx +5 -5
- package/src/components/ui/config-card.tsx +71 -0
- package/src/components/ui/dialog.tsx +1 -1
- package/src/components/ui/input.tsx +2 -2
- package/src/components/ui/select.tsx +1 -1
- package/src/components/ui/status-dot.tsx +51 -0
- package/src/components/ui/switch.tsx +2 -2
- package/src/components/ui/tabs-custom.tsx +9 -6
- package/src/components/ui/tabs.tsx +3 -3
- package/src/index.css +25 -37
- package/src/styles/design-system.css +53 -49
- package/dist/assets/index-B3foa-xK.css +0 -1
- package/dist/assets/index-CsMwBztg.js +0 -347
|
@@ -142,8 +142,8 @@ function matchInstalledSearch(
|
|
|
142
142
|
|
|
143
143
|
function getAvatarColor(text: string) {
|
|
144
144
|
const colors = [
|
|
145
|
-
'bg-
|
|
146
|
-
'bg-
|
|
145
|
+
'bg-amber-600', 'bg-orange-500', 'bg-yellow-600', 'bg-emerald-600',
|
|
146
|
+
'bg-teal-600', 'bg-cyan-600', 'bg-stone-600', 'bg-rose-500', 'bg-violet-500'
|
|
147
147
|
];
|
|
148
148
|
let hash = 0;
|
|
149
149
|
for (let i = 0; i < text.length; i++) {
|
|
@@ -157,7 +157,7 @@ function ItemIcon({ name, fallback }: { name?: string; fallback: string }) {
|
|
|
157
157
|
const letters = displayName.substring(0, 2).toUpperCase();
|
|
158
158
|
const colorClass = getAvatarColor(displayName);
|
|
159
159
|
return (
|
|
160
|
-
<div className={cn("flex items-center justify-center w-
|
|
160
|
+
<div className={cn("flex items-center justify-center w-10 h-10 rounded-xl text-white font-semibold text-sm shrink-0", colorClass)}>
|
|
161
161
|
{letters}
|
|
162
162
|
</div>
|
|
163
163
|
);
|
|
@@ -173,7 +173,7 @@ function FilterPanel(props: {
|
|
|
173
173
|
onSortChange: (value: MarketplaceSort) => void;
|
|
174
174
|
}) {
|
|
175
175
|
return (
|
|
176
|
-
<div className="
|
|
176
|
+
<div className="mb-4">
|
|
177
177
|
<div className="flex gap-3 items-center">
|
|
178
178
|
<div className="flex-1 min-w-0 relative">
|
|
179
179
|
<PackageSearch className="h-4 w-4 text-gray-400 absolute left-3 top-1/2 -translate-y-1/2" />
|
|
@@ -181,11 +181,11 @@ function FilterPanel(props: {
|
|
|
181
181
|
value={props.searchText}
|
|
182
182
|
onChange={(event) => props.onSearchTextChange(event.target.value)}
|
|
183
183
|
placeholder="Search extensions..."
|
|
184
|
-
className="w-full h-9 border border-gray-200 rounded-
|
|
184
|
+
className="w-full h-9 border border-gray-200/80 rounded-xl pl-9 pr-3 text-sm focus:outline-none focus:ring-1 focus:ring-primary/40"
|
|
185
185
|
/>
|
|
186
186
|
</div>
|
|
187
187
|
|
|
188
|
-
<div className="inline-flex h-9 rounded-
|
|
188
|
+
<div className="inline-flex h-9 rounded-xl bg-gray-100/80 p-1 shrink-0">
|
|
189
189
|
{([
|
|
190
190
|
{ value: 'all', label: 'All' },
|
|
191
191
|
{ value: 'plugin', label: 'Plugins' },
|
|
@@ -196,7 +196,7 @@ function FilterPanel(props: {
|
|
|
196
196
|
type="button"
|
|
197
197
|
onClick={() => props.onTypeFilterChange(opt.value)}
|
|
198
198
|
className={cn(
|
|
199
|
-
'px-3 rounded-
|
|
199
|
+
'px-3 rounded-lg text-sm font-medium transition-all whitespace-nowrap',
|
|
200
200
|
props.typeFilter === opt.value
|
|
201
201
|
? 'bg-white text-gray-900 shadow-sm'
|
|
202
202
|
: 'text-gray-500 hover:text-gray-700'
|
|
@@ -252,7 +252,7 @@ function MarketplaceListCard(props: {
|
|
|
252
252
|
const displayType = type === 'plugin' ? 'Plugin' : type === 'skill' ? 'Skill' : 'Extension';
|
|
253
253
|
|
|
254
254
|
return (
|
|
255
|
-
<article className="group bg-white border border-
|
|
255
|
+
<article className="group bg-white border border-gray-200/40 hover:border-gray-200/80 rounded-2xl px-5 py-4 hover:shadow-md shadow-sm transition-all flex items-start gap-3.5 justify-between cursor-default">
|
|
256
256
|
<div className="flex gap-3 min-w-0 flex-1 h-full items-start">
|
|
257
257
|
<ItemIcon name={title} fallback={spec || 'Ext'} />
|
|
258
258
|
<div className="min-w-0 flex-1 flex flex-col justify-center h-full">
|
|
@@ -302,7 +302,7 @@ function MarketplaceListCard(props: {
|
|
|
302
302
|
<button
|
|
303
303
|
onClick={() => props.onInstall(props.item as MarketplaceItemSummary)}
|
|
304
304
|
disabled={props.installState.isPending}
|
|
305
|
-
className="inline-flex items-center gap-1.5 h-8 px-4 rounded-
|
|
305
|
+
className="inline-flex items-center gap-1.5 h-8 px-4 rounded-xl text-xs font-medium bg-primary text-white hover:bg-primary-600 disabled:opacity-50 transition-colors"
|
|
306
306
|
>
|
|
307
307
|
{isInstalling ? 'Installing...' : 'Install'}
|
|
308
308
|
</button>
|
|
@@ -312,7 +312,7 @@ function MarketplaceListCard(props: {
|
|
|
312
312
|
<button
|
|
313
313
|
disabled={props.manageState.isPending}
|
|
314
314
|
onClick={() => props.onManage(isDisabled ? 'enable' : 'disable', pluginRecord)}
|
|
315
|
-
className="inline-flex items-center h-8 px-4 rounded-
|
|
315
|
+
className="inline-flex items-center h-8 px-4 rounded-xl text-xs font-medium border border-gray-200/80 text-gray-600 bg-white hover:bg-gray-50 hover:border-gray-300 disabled:opacity-50 transition-colors"
|
|
316
316
|
>
|
|
317
317
|
{busyForRecord && props.manageState.action !== 'uninstall'
|
|
318
318
|
? (props.manageState.action === 'enable' ? 'Enabling...' : 'Disabling...')
|
|
@@ -324,7 +324,7 @@ function MarketplaceListCard(props: {
|
|
|
324
324
|
<button
|
|
325
325
|
disabled={props.manageState.isPending}
|
|
326
326
|
onClick={() => props.onManage('uninstall', record)}
|
|
327
|
-
className="inline-flex items-center h-8 px-4 rounded-
|
|
327
|
+
className="inline-flex items-center h-8 px-4 rounded-xl text-xs font-medium border border-rose-100 text-rose-500 bg-white hover:bg-rose-50 hover:border-rose-200 disabled:opacity-50 transition-colors"
|
|
328
328
|
>
|
|
329
329
|
{busyForRecord && props.manageState.action === 'uninstall' ? 'Removing...' : 'Uninstall'}
|
|
330
330
|
</button>
|
|
@@ -344,7 +344,7 @@ function PaginationBar(props: {
|
|
|
344
344
|
return (
|
|
345
345
|
<div className="mt-4 flex items-center justify-end gap-2">
|
|
346
346
|
<button
|
|
347
|
-
className="h-8 px-3 rounded-
|
|
347
|
+
className="h-8 px-3 rounded-xl border border-gray-200/80 text-sm text-gray-600 disabled:opacity-40"
|
|
348
348
|
onClick={props.onPrev}
|
|
349
349
|
disabled={props.page <= 1 || props.busy}
|
|
350
350
|
>
|
|
@@ -354,7 +354,7 @@ function PaginationBar(props: {
|
|
|
354
354
|
{props.totalPages === 0 ? '0 / 0' : `${props.page} / ${props.totalPages}`}
|
|
355
355
|
</div>
|
|
356
356
|
<button
|
|
357
|
-
className="h-8 px-3 rounded-
|
|
357
|
+
className="h-8 px-3 rounded-xl border border-gray-200/80 text-sm text-gray-600 disabled:opacity-40"
|
|
358
358
|
onClick={props.onNext}
|
|
359
359
|
disabled={props.totalPages === 0 || props.page >= props.totalPages || props.busy}
|
|
360
360
|
>
|
|
@@ -513,9 +513,9 @@ export function MarketplacePage() {
|
|
|
513
513
|
|
|
514
514
|
return (
|
|
515
515
|
<div className="animate-fade-in pb-20">
|
|
516
|
-
<div className="mb-
|
|
517
|
-
<h2 className="text-
|
|
518
|
-
<p className="text-[
|
|
516
|
+
<div className="mb-4">
|
|
517
|
+
<h2 className="text-xl font-semibold text-gray-900">Marketplace</h2>
|
|
518
|
+
<p className="text-[12px] text-gray-400 mt-0.5">A cleaner extension list focused on install / enable / disable.</p>
|
|
519
519
|
</div>
|
|
520
520
|
|
|
521
521
|
<Tabs
|
|
@@ -546,7 +546,7 @@ export function MarketplacePage() {
|
|
|
546
546
|
|
|
547
547
|
<section>
|
|
548
548
|
<div className="flex items-center justify-between mb-3">
|
|
549
|
-
<h3 className="text-[
|
|
549
|
+
<h3 className="text-[14px] font-semibold text-gray-900">{scope === 'installed' ? 'Installed' : 'Extensions'}</h3>
|
|
550
550
|
<span className="text-[12px] text-gray-500">{listSummary}</span>
|
|
551
551
|
</div>
|
|
552
552
|
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
import { cn } from '@/lib/utils';
|
|
2
|
+
import { ArrowRight } from 'lucide-react';
|
|
3
|
+
|
|
4
|
+
interface ActionLinkProps {
|
|
5
|
+
label: string;
|
|
6
|
+
className?: string;
|
|
7
|
+
onClick?: () => void;
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
/**
|
|
11
|
+
* Unified action link with arrow indicator.
|
|
12
|
+
* Used in card footers for "Configure →", "Enable →", etc.
|
|
13
|
+
*/
|
|
14
|
+
export function ActionLink({ label, className, onClick }: ActionLinkProps) {
|
|
15
|
+
return (
|
|
16
|
+
<span
|
|
17
|
+
onClick={onClick}
|
|
18
|
+
className={cn(
|
|
19
|
+
'inline-flex items-center gap-1 text-[13px] font-medium text-gray-600 hover:text-primary transition-colors cursor-pointer group/action',
|
|
20
|
+
className
|
|
21
|
+
)}
|
|
22
|
+
>
|
|
23
|
+
{label}
|
|
24
|
+
<ArrowRight className="h-3 w-3 transition-transform group-hover/action:translate-x-0.5" />
|
|
25
|
+
</span>
|
|
26
|
+
);
|
|
27
|
+
}
|
|
@@ -3,27 +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-xl 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
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-
|
|
13
|
-
secondary: 'bg-
|
|
14
|
-
ghost: 'hover:bg-
|
|
12
|
+
outline: 'border border-gray-200 bg-white hover:bg-gray-50 hover:text-gray-800 text-gray-600',
|
|
13
|
+
secondary: 'bg-gray-100 text-gray-700 hover:bg-gray-200/80',
|
|
14
|
+
ghost: 'hover:bg-gray-100/80 hover:text-gray-800',
|
|
15
15
|
link: 'text-primary underline-offset-4 hover:underline',
|
|
16
|
-
// New variants matching the design
|
|
17
16
|
primary: 'bg-primary text-primary-foreground hover:bg-primary-600 active:bg-primary-700 shadow-sm',
|
|
18
|
-
subtle: 'bg-
|
|
19
|
-
'primary-outline': 'border
|
|
17
|
+
subtle: 'bg-gray-100 text-gray-600 hover:bg-gray-200/80',
|
|
18
|
+
'primary-outline': 'border border-primary/30 text-primary hover:bg-primary hover:text-primary-foreground'
|
|
20
19
|
},
|
|
21
20
|
size: {
|
|
22
|
-
default: 'h-
|
|
23
|
-
sm: 'h-8 rounded-
|
|
24
|
-
lg: 'h-
|
|
25
|
-
xl: 'h-
|
|
26
|
-
icon: 'h-
|
|
21
|
+
default: 'h-9 px-4 py-2',
|
|
22
|
+
sm: 'h-8 rounded-lg px-3 text-xs',
|
|
23
|
+
lg: 'h-11 rounded-xl px-5 text-[14px]',
|
|
24
|
+
xl: 'h-12 rounded-2xl px-6 text-[15px]',
|
|
25
|
+
icon: 'h-9 w-9 rounded-xl'
|
|
27
26
|
}
|
|
28
27
|
},
|
|
29
28
|
defaultVariants: {
|
|
@@ -35,7 +34,7 @@ const buttonVariants = cva(
|
|
|
35
34
|
|
|
36
35
|
export interface ButtonProps
|
|
37
36
|
extends React.ButtonHTMLAttributes<HTMLButtonElement>,
|
|
38
|
-
|
|
37
|
+
VariantProps<typeof buttonVariants> {
|
|
39
38
|
asChild?: boolean;
|
|
40
39
|
}
|
|
41
40
|
|
|
@@ -8,8 +8,8 @@ const Card = React.forwardRef<
|
|
|
8
8
|
<div
|
|
9
9
|
ref={ref}
|
|
10
10
|
className={cn(
|
|
11
|
-
'rounded-
|
|
12
|
-
hover && 'hover:shadow-
|
|
11
|
+
'rounded-2xl border border-gray-200/50 bg-white text-card-foreground shadow-sm transition-all duration-base',
|
|
12
|
+
hover && 'hover:shadow-md hover:border-gray-200',
|
|
13
13
|
className
|
|
14
14
|
)}
|
|
15
15
|
{...props}
|
|
@@ -23,7 +23,7 @@ const CardHeader = React.forwardRef<
|
|
|
23
23
|
>(({ className, ...props }, ref) => (
|
|
24
24
|
<div
|
|
25
25
|
ref={ref}
|
|
26
|
-
className={cn('flex flex-col space-y-
|
|
26
|
+
className={cn('flex flex-col space-y-1.5 p-6', className)}
|
|
27
27
|
{...props}
|
|
28
28
|
/>
|
|
29
29
|
));
|
|
@@ -36,7 +36,7 @@ const CardTitle = React.forwardRef<
|
|
|
36
36
|
<h3
|
|
37
37
|
ref={ref}
|
|
38
38
|
className={cn(
|
|
39
|
-
'text-
|
|
39
|
+
'text-[15px] font-semibold leading-tight tracking-tight text-gray-900',
|
|
40
40
|
className
|
|
41
41
|
)}
|
|
42
42
|
{...props}
|
|
@@ -50,7 +50,7 @@ const CardDescription = React.forwardRef<
|
|
|
50
50
|
>(({ className, ...props }, ref) => (
|
|
51
51
|
<p
|
|
52
52
|
ref={ref}
|
|
53
|
-
className={cn('text-
|
|
53
|
+
className={cn('text-[13px] text-gray-400 leading-relaxed', className)}
|
|
54
54
|
{...props}
|
|
55
55
|
/>
|
|
56
56
|
));
|
|
@@ -0,0 +1,71 @@
|
|
|
1
|
+
import * as React from 'react';
|
|
2
|
+
import { cn } from '@/lib/utils';
|
|
3
|
+
|
|
4
|
+
interface ConfigCardProps {
|
|
5
|
+
children: React.ReactNode;
|
|
6
|
+
onClick?: () => void;
|
|
7
|
+
className?: string;
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
/**
|
|
11
|
+
* Unified config card used for Channels, Providers, etc.
|
|
12
|
+
* Style follows YouMind: generous padding, large radius, soft shadow.
|
|
13
|
+
*/
|
|
14
|
+
export function ConfigCard({ children, onClick, className }: ConfigCardProps) {
|
|
15
|
+
return (
|
|
16
|
+
<div
|
|
17
|
+
onClick={onClick}
|
|
18
|
+
className={cn(
|
|
19
|
+
'group relative flex flex-col p-6 rounded-2xl border border-gray-200/50 bg-white shadow-sm',
|
|
20
|
+
'transition-all duration-base cursor-pointer',
|
|
21
|
+
'hover:shadow-md hover:border-gray-200',
|
|
22
|
+
className
|
|
23
|
+
)}
|
|
24
|
+
>
|
|
25
|
+
{children}
|
|
26
|
+
</div>
|
|
27
|
+
);
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
interface ConfigCardHeaderProps {
|
|
31
|
+
children: React.ReactNode;
|
|
32
|
+
className?: string;
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
export function ConfigCardHeader({ children, className }: ConfigCardHeaderProps) {
|
|
36
|
+
return (
|
|
37
|
+
<div className={cn('flex items-start justify-between mb-4', className)}>
|
|
38
|
+
{children}
|
|
39
|
+
</div>
|
|
40
|
+
);
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
interface ConfigCardBodyProps {
|
|
44
|
+
title: string;
|
|
45
|
+
description?: string;
|
|
46
|
+
className?: string;
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
export function ConfigCardBody({ title, description, className }: ConfigCardBodyProps) {
|
|
50
|
+
return (
|
|
51
|
+
<div className={cn('flex-1', className)}>
|
|
52
|
+
<h3 className="text-[14px] font-bold text-gray-900 mb-0.5">{title}</h3>
|
|
53
|
+
{description && (
|
|
54
|
+
<p className="text-[12px] text-gray-400 leading-relaxed line-clamp-2">{description}</p>
|
|
55
|
+
)}
|
|
56
|
+
</div>
|
|
57
|
+
);
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
interface ConfigCardFooterProps {
|
|
61
|
+
children: React.ReactNode;
|
|
62
|
+
className?: string;
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
export function ConfigCardFooter({ children, className }: ConfigCardFooterProps) {
|
|
66
|
+
return (
|
|
67
|
+
<div className={cn('mt-4 pt-3 flex items-center justify-between', className)}>
|
|
68
|
+
{children}
|
|
69
|
+
</div>
|
|
70
|
+
);
|
|
71
|
+
}
|
|
@@ -36,7 +36,7 @@ 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-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-
|
|
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/50 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-2xl",
|
|
40
40
|
className
|
|
41
41
|
)}
|
|
42
42
|
{...props}
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import * as React from 'react';
|
|
2
2
|
import { cn } from '@/lib/utils';
|
|
3
3
|
|
|
4
|
-
export interface InputProps extends React.InputHTMLAttributes<HTMLInputElement> {}
|
|
4
|
+
export interface InputProps extends React.InputHTMLAttributes<HTMLInputElement> { }
|
|
5
5
|
|
|
6
6
|
const Input = React.forwardRef<HTMLInputElement, InputProps>(
|
|
7
7
|
({ className, type, ...props }, ref) => {
|
|
@@ -9,7 +9,7 @@ const Input = React.forwardRef<HTMLInputElement, InputProps>(
|
|
|
9
9
|
<input
|
|
10
10
|
type={type}
|
|
11
11
|
className={cn(
|
|
12
|
-
'flex h-
|
|
12
|
+
'flex h-9 w-full rounded-xl border border-gray-200/80 bg-white px-3.5 py-2 text-sm file:border-0 file:bg-transparent file:text-sm file:font-medium placeholder:text-gray-400 focus:outline-none focus:ring-1 focus:ring-primary/40 focus:border-primary/40 transition-colors disabled:cursor-not-allowed disabled:opacity-50',
|
|
13
13
|
className
|
|
14
14
|
)}
|
|
15
15
|
ref={ref}
|
|
@@ -14,7 +14,7 @@ const SelectTrigger = React.forwardRef<
|
|
|
14
14
|
<SelectPrimitive.Trigger
|
|
15
15
|
ref={ref}
|
|
16
16
|
className={cn(
|
|
17
|
-
"flex h-9 w-full items-center justify-between whitespace-nowrap rounded-
|
|
17
|
+
"flex h-9 w-full items-center justify-between whitespace-nowrap rounded-xl border border-gray-200/80 bg-transparent px-3 py-2 text-sm shadow-sm ring-offset-background placeholder:text-muted-foreground focus:outline-none focus:ring-1 focus:ring-primary/40 disabled:cursor-not-allowed disabled:opacity-50 [&>span]:line-clamp-1 bg-white",
|
|
18
18
|
className
|
|
19
19
|
)}
|
|
20
20
|
{...props}
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
import { cn } from '@/lib/utils';
|
|
2
|
+
|
|
3
|
+
type StatusType = 'active' | 'inactive' | 'ready' | 'setup' | 'warning';
|
|
4
|
+
|
|
5
|
+
interface StatusDotProps {
|
|
6
|
+
status: StatusType;
|
|
7
|
+
label: string;
|
|
8
|
+
className?: string;
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
const statusStyles: Record<StatusType, { dot: string; text: string; bg: string }> = {
|
|
12
|
+
active: {
|
|
13
|
+
dot: 'bg-emerald-500',
|
|
14
|
+
text: 'text-emerald-600',
|
|
15
|
+
bg: 'bg-emerald-50',
|
|
16
|
+
},
|
|
17
|
+
ready: {
|
|
18
|
+
dot: 'bg-emerald-500',
|
|
19
|
+
text: 'text-emerald-600',
|
|
20
|
+
bg: 'bg-emerald-50',
|
|
21
|
+
},
|
|
22
|
+
inactive: {
|
|
23
|
+
dot: 'bg-gray-300',
|
|
24
|
+
text: 'text-gray-400',
|
|
25
|
+
bg: 'bg-gray-100/80',
|
|
26
|
+
},
|
|
27
|
+
setup: {
|
|
28
|
+
dot: 'bg-gray-300',
|
|
29
|
+
text: 'text-gray-400',
|
|
30
|
+
bg: 'bg-gray-100/80',
|
|
31
|
+
},
|
|
32
|
+
warning: {
|
|
33
|
+
dot: 'bg-amber-400',
|
|
34
|
+
text: 'text-amber-600',
|
|
35
|
+
bg: 'bg-amber-50',
|
|
36
|
+
},
|
|
37
|
+
};
|
|
38
|
+
|
|
39
|
+
/**
|
|
40
|
+
* Unified status indicator with dot + label.
|
|
41
|
+
* Used consistently across Channels, Providers, etc.
|
|
42
|
+
*/
|
|
43
|
+
export function StatusDot({ status, label, className }: StatusDotProps) {
|
|
44
|
+
const style = statusStyles[status];
|
|
45
|
+
return (
|
|
46
|
+
<div className={cn('flex items-center gap-1.5 px-2 py-0.5 rounded-full', style.bg, className)}>
|
|
47
|
+
<span className={cn('h-1.5 w-1.5 rounded-full', style.dot)} />
|
|
48
|
+
<span className={cn('text-[11px] font-medium', style.text)}>{label}</span>
|
|
49
|
+
</div>
|
|
50
|
+
);
|
|
51
|
+
}
|
|
@@ -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-
|
|
19
|
-
checked ? 'bg-primary' : 'bg-gray-200 hover:bg-gray-300',
|
|
18
|
+
'peer inline-flex h-[22px] w-10 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/80 hover:bg-gray-300/80',
|
|
20
20
|
className
|
|
21
21
|
)}
|
|
22
22
|
onClick={() => onCheckedChange?.(!checked)}
|
|
@@ -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-
|
|
19
|
+
<div className={cn('flex items-center gap-6 border-b border-gray-200/60 mb-6', className)}>
|
|
20
20
|
{tabs.map((tab) => {
|
|
21
21
|
const isActive = activeTab === tab.id;
|
|
22
22
|
return (
|
|
@@ -24,18 +24,21 @@ 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-
|
|
27
|
+
'relative pb-3 text-[14px] font-medium transition-all duration-fast flex items-center gap-1.5',
|
|
28
28
|
isActive
|
|
29
|
-
? 'text-
|
|
30
|
-
: 'text-gray-
|
|
29
|
+
? 'text-gray-900'
|
|
30
|
+
: 'text-gray-600 hover:text-gray-900'
|
|
31
31
|
)}
|
|
32
32
|
>
|
|
33
33
|
{tab.label}
|
|
34
34
|
{tab.count !== undefined && (
|
|
35
|
-
<span className=
|
|
35
|
+
<span className={cn(
|
|
36
|
+
'text-[11px] font-medium',
|
|
37
|
+
isActive ? 'text-gray-500' : 'text-gray-500'
|
|
38
|
+
)}>{tab.count.toLocaleString()}</span>
|
|
36
39
|
)}
|
|
37
40
|
{isActive && (
|
|
38
|
-
<div className="absolute bottom-0 left-0 right-0 h-
|
|
41
|
+
<div className="absolute bottom-0 left-0 right-0 h-[2px] bg-primary rounded-full" />
|
|
39
42
|
)}
|
|
40
43
|
</button>
|
|
41
44
|
);
|
|
@@ -42,7 +42,7 @@ export function TabsList({ children, className }: TabsListProps) {
|
|
|
42
42
|
return (
|
|
43
43
|
<div
|
|
44
44
|
className={cn(
|
|
45
|
-
'inline-flex h-
|
|
45
|
+
'inline-flex h-9 items-center justify-center rounded-xl bg-gray-100/80 p-1 text-gray-500',
|
|
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-md px-3 py-1
|
|
65
|
+
'inline-flex items-center justify-center whitespace-nowrap rounded-md px-3 py-1 text-[13px] 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
67
|
? 'bg-white text-gray-900 shadow-sm'
|
|
68
|
-
: 'hover:bg-white/50 hover:text-gray-
|
|
68
|
+
: 'hover:bg-white/50 hover:text-gray-800 text-gray-600',
|
|
69
69
|
className
|
|
70
70
|
)}
|
|
71
71
|
>
|
package/src/index.css
CHANGED
|
@@ -19,43 +19,31 @@
|
|
|
19
19
|
--popover: 0 0% 100%;
|
|
20
20
|
--popover-foreground: 221 39% 11%;
|
|
21
21
|
|
|
22
|
-
/* Primary:
|
|
22
|
+
/* Primary: Blue */
|
|
23
23
|
--primary: 217 80% 55%;
|
|
24
24
|
--primary-foreground: 0 0% 100%;
|
|
25
25
|
|
|
26
|
-
/* Secondary
|
|
27
|
-
--secondary:
|
|
28
|
-
--secondary-foreground:
|
|
26
|
+
/* Secondary */
|
|
27
|
+
--secondary: 214 18% 96%;
|
|
28
|
+
--secondary-foreground: 219 18% 18%;
|
|
29
29
|
|
|
30
30
|
/* Muted */
|
|
31
|
-
--muted:
|
|
32
|
-
--muted-foreground:
|
|
31
|
+
--muted: 214 18% 96%;
|
|
32
|
+
--muted-foreground: 216 6% 46%;
|
|
33
33
|
|
|
34
34
|
/* Accent */
|
|
35
35
|
--accent: 217 100% 97%;
|
|
36
|
-
--accent-foreground: 217
|
|
36
|
+
--accent-foreground: 217 60% 35%;
|
|
37
37
|
|
|
38
38
|
/* Destructive */
|
|
39
39
|
--destructive: 0 84% 60%;
|
|
40
40
|
--destructive-foreground: 0 0% 98%;
|
|
41
41
|
|
|
42
42
|
/* UI Elements */
|
|
43
|
-
--border:
|
|
44
|
-
--input:
|
|
43
|
+
--border: 213 14% 90%;
|
|
44
|
+
--input: 213 14% 90%;
|
|
45
45
|
--ring: 217 80% 55%;
|
|
46
46
|
--radius: 0.75rem;
|
|
47
|
-
|
|
48
|
-
/* Legacy compatibility */
|
|
49
|
-
--milk-50: 210 20% 98%;
|
|
50
|
-
--milk-100: 220 14% 96%;
|
|
51
|
-
--milk-200: 220 13% 91%;
|
|
52
|
-
--milk-300: 216 12% 84%;
|
|
53
|
-
--milk-400: 218 11% 65%;
|
|
54
|
-
--milk-500: 220 9% 46%;
|
|
55
|
-
--milk-600: 215 14% 34%;
|
|
56
|
-
--milk-700: 217 19% 27%;
|
|
57
|
-
--milk-800: 215 28% 17%;
|
|
58
|
-
--milk-900: 221 39% 11%;
|
|
59
47
|
}
|
|
60
48
|
}
|
|
61
49
|
|
|
@@ -89,8 +77,8 @@
|
|
|
89
77
|
SCROLLBAR
|
|
90
78
|
======================================== */
|
|
91
79
|
.custom-scrollbar::-webkit-scrollbar {
|
|
92
|
-
width:
|
|
93
|
-
height:
|
|
80
|
+
width: 5px;
|
|
81
|
+
height: 5px;
|
|
94
82
|
}
|
|
95
83
|
|
|
96
84
|
.custom-scrollbar::-webkit-scrollbar-track {
|
|
@@ -110,14 +98,14 @@
|
|
|
110
98
|
GLASSMORPHISM
|
|
111
99
|
======================================== */
|
|
112
100
|
.glass {
|
|
113
|
-
background: rgba(255, 255, 255, 0.
|
|
101
|
+
background: rgba(255, 255, 255, 0.85);
|
|
114
102
|
backdrop-filter: blur(12px);
|
|
115
103
|
-webkit-backdrop-filter: blur(12px);
|
|
116
|
-
border: 1px solid rgba(255, 255, 255, 0.
|
|
104
|
+
border: 1px solid rgba(255, 255, 255, 0.3);
|
|
117
105
|
}
|
|
118
106
|
|
|
119
107
|
.glass-dark {
|
|
120
|
-
background: rgba(0, 0, 0, 0.
|
|
108
|
+
background: rgba(0, 0, 0, 0.06);
|
|
121
109
|
backdrop-filter: blur(12px);
|
|
122
110
|
-webkit-backdrop-filter: blur(12px);
|
|
123
111
|
border: 1px solid rgba(255, 255, 255, 0.1);
|
|
@@ -127,19 +115,19 @@
|
|
|
127
115
|
SHADOWS
|
|
128
116
|
======================================== */
|
|
129
117
|
.shadow-card {
|
|
130
|
-
box-shadow: 0
|
|
118
|
+
box-shadow: 0 1px 3px -1px rgba(30, 20, 10, 0.03), 0 1px 2px -1px rgba(30, 20, 10, 0.02);
|
|
131
119
|
}
|
|
132
120
|
|
|
133
121
|
.shadow-card-hover {
|
|
134
|
-
box-shadow: 0
|
|
122
|
+
box-shadow: 0 8px 20px -4px rgba(30, 20, 10, 0.06), 0 4px 8px -4px rgba(30, 20, 10, 0.03);
|
|
135
123
|
}
|
|
136
124
|
|
|
137
125
|
.shadow-premium {
|
|
138
|
-
box-shadow: 0
|
|
126
|
+
box-shadow: 0 8px 24px -8px rgba(30, 20, 10, 0.06), 0 4px 10px -4px rgba(30, 20, 10, 0.03);
|
|
139
127
|
}
|
|
140
128
|
|
|
141
129
|
.shadow-premium-hover {
|
|
142
|
-
box-shadow: 0
|
|
130
|
+
box-shadow: 0 16px 32px -12px rgba(30, 20, 10, 0.1), 0 6px 12px -4px rgba(30, 20, 10, 0.04);
|
|
143
131
|
}
|
|
144
132
|
|
|
145
133
|
/* ========================================
|
|
@@ -169,7 +157,7 @@
|
|
|
169
157
|
@keyframes fadeIn {
|
|
170
158
|
from {
|
|
171
159
|
opacity: 0;
|
|
172
|
-
transform: translateY(
|
|
160
|
+
transform: translateY(8px);
|
|
173
161
|
}
|
|
174
162
|
|
|
175
163
|
to {
|
|
@@ -181,7 +169,7 @@
|
|
|
181
169
|
@keyframes slideIn {
|
|
182
170
|
from {
|
|
183
171
|
opacity: 0;
|
|
184
|
-
transform: translateX(-
|
|
172
|
+
transform: translateX(-8px);
|
|
185
173
|
}
|
|
186
174
|
|
|
187
175
|
to {
|
|
@@ -193,7 +181,7 @@
|
|
|
193
181
|
@keyframes scaleIn {
|
|
194
182
|
from {
|
|
195
183
|
opacity: 0;
|
|
196
|
-
transform: scale(0.
|
|
184
|
+
transform: scale(0.98);
|
|
197
185
|
}
|
|
198
186
|
|
|
199
187
|
to {
|
|
@@ -210,20 +198,20 @@
|
|
|
210
198
|
}
|
|
211
199
|
|
|
212
200
|
50% {
|
|
213
|
-
opacity: 0.
|
|
201
|
+
opacity: 0.85;
|
|
214
202
|
}
|
|
215
203
|
}
|
|
216
204
|
|
|
217
205
|
.animate-fade-in {
|
|
218
|
-
animation: fadeIn 0.
|
|
206
|
+
animation: fadeIn 0.35s cubic-bezier(0.16, 1, 0.3, 1) forwards;
|
|
219
207
|
}
|
|
220
208
|
|
|
221
209
|
.animate-slide-in {
|
|
222
|
-
animation: slideIn 0.
|
|
210
|
+
animation: slideIn 0.3s cubic-bezier(0.16, 1, 0.3, 1) forwards;
|
|
223
211
|
}
|
|
224
212
|
|
|
225
213
|
.animate-scale-in {
|
|
226
|
-
animation: scaleIn 0.
|
|
214
|
+
animation: scaleIn 0.3s cubic-bezier(0.16, 1, 0.3, 1) forwards;
|
|
227
215
|
}
|
|
228
216
|
|
|
229
217
|
.animate-pulse-soft {
|