@zhin.js/console 1.0.20 → 1.0.22
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 +24 -0
- package/README.md +4 -4
- package/client/components.json +17 -0
- package/client/index.html +1 -1
- package/client/src/components/PluginConfigForm/BasicFieldRenderers.tsx +89 -180
- package/client/src/components/PluginConfigForm/CollectionFieldRenderers.tsx +97 -200
- package/client/src/components/PluginConfigForm/CompositeFieldRenderers.tsx +31 -70
- package/client/src/components/PluginConfigForm/FieldRenderer.tsx +27 -77
- package/client/src/components/PluginConfigForm/NestedFieldRenderer.tsx +33 -53
- package/client/src/components/PluginConfigForm/index.tsx +71 -173
- package/client/src/components/ui/accordion.tsx +54 -0
- package/client/src/components/ui/alert.tsx +62 -0
- package/client/src/components/ui/avatar.tsx +41 -0
- package/client/src/components/ui/badge.tsx +32 -0
- package/client/src/components/ui/button.tsx +50 -0
- package/client/src/components/ui/card.tsx +50 -0
- package/client/src/components/ui/checkbox.tsx +25 -0
- package/client/src/components/ui/dialog.tsx +87 -0
- package/client/src/components/ui/dropdown-menu.tsx +97 -0
- package/client/src/components/ui/input.tsx +21 -0
- package/client/src/components/ui/scroll-area.tsx +43 -0
- package/client/src/components/ui/select.tsx +127 -0
- package/client/src/components/ui/separator.tsx +23 -0
- package/client/src/components/ui/skeleton.tsx +12 -0
- package/client/src/components/ui/switch.tsx +26 -0
- package/client/src/components/ui/tabs.tsx +52 -0
- package/client/src/components/ui/textarea.tsx +20 -0
- package/client/src/components/ui/tooltip.tsx +27 -0
- package/client/src/layouts/dashboard.tsx +91 -221
- package/client/src/main.tsx +38 -42
- package/client/src/pages/dashboard-bots.tsx +91 -137
- package/client/src/pages/dashboard-home.tsx +133 -204
- package/client/src/pages/dashboard-logs.tsx +125 -196
- package/client/src/pages/dashboard-plugin-detail.tsx +261 -329
- package/client/src/pages/dashboard-plugins.tsx +108 -105
- package/client/src/style.css +156 -865
- package/client/src/theme/index.ts +60 -35
- package/client/tailwind.config.js +78 -69
- package/dist/client.js +1 -1
- package/dist/cva.js +47 -0
- package/dist/index.html +1 -1
- package/dist/index.js +6 -6
- package/dist/react-router.js +7121 -5585
- package/dist/react.js +192 -149
- package/dist/style.css +2 -2
- package/lib/bin.js +2 -2
- package/lib/build.js +2 -2
- package/lib/index.d.ts +0 -3
- package/lib/index.js +160 -205
- package/lib/transform.d.ts +26 -0
- package/lib/transform.js +78 -0
- package/lib/websocket.d.ts +0 -1
- package/package.json +8 -7
- package/dist/radix-ui-themes.js +0 -9305
- package/lib/dev.d.ts +0 -18
- package/lib/dev.js +0 -87
|
@@ -1,10 +1,12 @@
|
|
|
1
1
|
/**
|
|
2
|
-
*
|
|
3
|
-
* 用于渲染数组项、元组项等嵌套字段
|
|
2
|
+
* Nested field renderer for array items, tuple slots, etc.
|
|
4
3
|
*/
|
|
5
4
|
|
|
6
|
-
import { Flex, Box, Text, TextField, TextArea, Switch, Card } from '@radix-ui/themes'
|
|
7
5
|
import type { SchemaField } from './types.js'
|
|
6
|
+
import { Input } from '../ui/input'
|
|
7
|
+
import { Textarea } from '../ui/textarea'
|
|
8
|
+
import { Switch } from '../ui/switch'
|
|
9
|
+
import { Card } from '../ui/card'
|
|
8
10
|
|
|
9
11
|
interface NestedFieldRendererProps {
|
|
10
12
|
fieldName: string
|
|
@@ -17,78 +19,56 @@ export function NestedFieldRenderer({ field, value, onChange }: NestedFieldRende
|
|
|
17
19
|
switch (field.type) {
|
|
18
20
|
case 'string':
|
|
19
21
|
return (
|
|
20
|
-
<
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
onChange={(e) => onChange(e.target.value)}
|
|
24
|
-
placeholder={field.description || '请输入'}
|
|
22
|
+
<Input
|
|
23
|
+
value={value || ''} onChange={(e) => onChange(e.target.value)}
|
|
24
|
+
placeholder={field.description || '请输入'} className="h-8 text-sm"
|
|
25
25
|
/>
|
|
26
26
|
)
|
|
27
|
-
|
|
27
|
+
|
|
28
28
|
case 'number':
|
|
29
29
|
case 'integer':
|
|
30
30
|
return (
|
|
31
|
-
<
|
|
32
|
-
|
|
33
|
-
type="number"
|
|
34
|
-
value={value?.toString() || ''}
|
|
31
|
+
<Input
|
|
32
|
+
type="number" value={value?.toString() || ''}
|
|
35
33
|
onChange={(e) => onChange(parseFloat(e.target.value) || 0)}
|
|
36
34
|
placeholder={field.description || '请输入数字'}
|
|
37
|
-
min={field.min}
|
|
38
|
-
max={field.max}
|
|
35
|
+
min={field.min} max={field.max} className="h-8 text-sm"
|
|
39
36
|
/>
|
|
40
37
|
)
|
|
41
|
-
|
|
38
|
+
|
|
42
39
|
case 'boolean':
|
|
43
40
|
return (
|
|
44
|
-
<
|
|
45
|
-
<Switch
|
|
46
|
-
|
|
47
|
-
onCheckedChange={onChange}
|
|
48
|
-
/>
|
|
49
|
-
<Text size="2" color={value ? 'green' : 'gray'}>
|
|
41
|
+
<div className="flex items-center gap-2">
|
|
42
|
+
<Switch checked={value === true} onCheckedChange={onChange} />
|
|
43
|
+
<span className={`text-sm ${value ? 'text-emerald-600 dark:text-emerald-400' : 'text-muted-foreground'}`}>
|
|
50
44
|
{value ? '已启用' : '已禁用'}
|
|
51
|
-
</
|
|
52
|
-
</
|
|
45
|
+
</span>
|
|
46
|
+
</div>
|
|
53
47
|
)
|
|
54
|
-
|
|
48
|
+
|
|
55
49
|
case 'object': {
|
|
56
50
|
const objectFields = field.dict || field.properties || {}
|
|
57
51
|
return (
|
|
58
|
-
<Card
|
|
59
|
-
|
|
60
|
-
{
|
|
61
|
-
<
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
onChange({ ...value, [key]: val })
|
|
69
|
-
}}
|
|
70
|
-
/>
|
|
71
|
-
</Box>
|
|
72
|
-
))}
|
|
73
|
-
</Flex>
|
|
52
|
+
<Card className="p-2 space-y-2">
|
|
53
|
+
{Object.entries(objectFields).map(([key, nestedField]: [string, any]) => (
|
|
54
|
+
<div key={key} className="space-y-1">
|
|
55
|
+
<span className="text-xs font-semibold">{key}</span>
|
|
56
|
+
<NestedFieldRenderer
|
|
57
|
+
fieldName={key} field={nestedField} value={value?.[key]}
|
|
58
|
+
onChange={(val) => onChange({ ...value, [key]: val })}
|
|
59
|
+
/>
|
|
60
|
+
</div>
|
|
61
|
+
))}
|
|
74
62
|
</Card>
|
|
75
63
|
)
|
|
76
64
|
}
|
|
77
|
-
|
|
65
|
+
|
|
78
66
|
default:
|
|
79
67
|
return (
|
|
80
|
-
<
|
|
81
|
-
size="1"
|
|
68
|
+
<Textarea
|
|
82
69
|
value={typeof value === 'object' ? JSON.stringify(value, null, 2) : value || ''}
|
|
83
|
-
onChange={(e) => {
|
|
84
|
-
|
|
85
|
-
onChange(JSON.parse(e.target.value))
|
|
86
|
-
} catch {
|
|
87
|
-
onChange(e.target.value)
|
|
88
|
-
}
|
|
89
|
-
}}
|
|
90
|
-
rows={3}
|
|
91
|
-
className="font-mono text-xs"
|
|
70
|
+
onChange={(e) => { try { onChange(JSON.parse(e.target.value)) } catch { onChange(e.target.value) } }}
|
|
71
|
+
rows={3} className="font-mono text-xs"
|
|
92
72
|
/>
|
|
93
73
|
)
|
|
94
74
|
}
|
|
@@ -1,57 +1,36 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* PluginConfigForm
|
|
3
|
-
* 插件配置表单 - 基于 Schema 自动生成
|
|
4
|
-
* 改为折叠面板形式,使用 WebSocket 传递配置
|
|
2
|
+
* PluginConfigForm - Plugin configuration form with accordion
|
|
5
3
|
*/
|
|
6
4
|
|
|
7
5
|
import { useState, useEffect } from 'react'
|
|
8
|
-
import {
|
|
9
|
-
Flex,
|
|
10
|
-
Box,
|
|
11
|
-
Text,
|
|
12
|
-
Button,
|
|
13
|
-
Spinner,
|
|
14
|
-
Callout,
|
|
15
|
-
Separator,
|
|
16
|
-
Badge,
|
|
17
|
-
ScrollArea,
|
|
18
|
-
Card
|
|
19
|
-
} from '@radix-ui/themes'
|
|
20
|
-
import { Accordion } from 'radix-ui'
|
|
21
6
|
import { useConfig } from '@zhin.js/client'
|
|
22
|
-
import type { PluginConfigFormProps, SchemaField
|
|
23
|
-
import { Settings, ChevronDown, CheckCircle, AlertCircle, X, Save } from 'lucide-react'
|
|
7
|
+
import type { PluginConfigFormProps, SchemaField } from './types.js'
|
|
8
|
+
import { Settings, ChevronDown, CheckCircle, AlertCircle, X, Save, Loader2 } from 'lucide-react'
|
|
24
9
|
import { FieldRenderer, isComplexField } from './FieldRenderer.js'
|
|
25
10
|
import { NestedFieldRenderer } from './NestedFieldRenderer.js'
|
|
11
|
+
import { Card } from '../ui/card'
|
|
12
|
+
import { Badge } from '../ui/badge'
|
|
13
|
+
import { Button } from '../ui/button'
|
|
14
|
+
import { Alert, AlertDescription } from '../ui/alert'
|
|
15
|
+
import { Accordion, AccordionItem, AccordionTrigger, AccordionContent } from '../ui/accordion'
|
|
26
16
|
|
|
27
17
|
export function PluginConfigForm({ pluginName, onSuccess }: Omit<PluginConfigFormProps, 'schema' | 'initialConfig'>) {
|
|
28
18
|
const [localConfig, setLocalConfig] = useState<Record<string, any>>({})
|
|
29
19
|
const [successMessage, setSuccessMessage] = useState<string | null>(null)
|
|
30
20
|
const [isExpanded, setIsExpanded] = useState<string | undefined>(undefined)
|
|
31
21
|
|
|
32
|
-
// 使用 WebSocket 配置管理
|
|
33
22
|
const { config, schema, loading, error, connected, setConfig } = useConfig(pluginName)
|
|
34
|
-
|
|
23
|
+
|
|
35
24
|
useEffect(() => {
|
|
36
|
-
if (config)
|
|
37
|
-
setLocalConfig(config)
|
|
38
|
-
}
|
|
25
|
+
if (config) setLocalConfig(config)
|
|
39
26
|
}, [config])
|
|
40
27
|
|
|
41
28
|
const handleSave = async () => {
|
|
42
|
-
if (!connected)
|
|
43
|
-
setSuccessMessage(null)
|
|
44
|
-
return
|
|
45
|
-
}
|
|
46
|
-
|
|
29
|
+
if (!connected) return
|
|
47
30
|
try {
|
|
48
31
|
await setConfig(localConfig)
|
|
49
32
|
setSuccessMessage('配置已保存成功')
|
|
50
|
-
setTimeout(() => {
|
|
51
|
-
setIsExpanded(undefined) // 收起面板
|
|
52
|
-
onSuccess?.()
|
|
53
|
-
setSuccessMessage(null)
|
|
54
|
-
}, 1500)
|
|
33
|
+
setTimeout(() => { setIsExpanded(undefined); onSuccess?.(); setSuccessMessage(null) }, 1500)
|
|
55
34
|
} catch (err) {
|
|
56
35
|
console.error('保存配置失败:', err)
|
|
57
36
|
}
|
|
@@ -64,10 +43,7 @@ export function PluginConfigForm({ pluginName, onSuccess }: Omit<PluginConfigFor
|
|
|
64
43
|
const handleNestedFieldChange = (parentPath: string, childKey: string, value: any) => {
|
|
65
44
|
setLocalConfig(prev => ({
|
|
66
45
|
...prev,
|
|
67
|
-
[parentPath]: {
|
|
68
|
-
...(prev[parentPath] || {}),
|
|
69
|
-
[childKey]: value
|
|
70
|
-
}
|
|
46
|
+
[parentPath]: { ...(prev[parentPath] || {}), [childKey]: value }
|
|
71
47
|
}))
|
|
72
48
|
}
|
|
73
49
|
|
|
@@ -80,8 +56,7 @@ export function PluginConfigForm({ pluginName, onSuccess }: Omit<PluginConfigFor
|
|
|
80
56
|
}
|
|
81
57
|
|
|
82
58
|
const renderField = (fieldName: string, field: SchemaField, parentPath?: string): React.ReactElement => {
|
|
83
|
-
const
|
|
84
|
-
const value = parentPath
|
|
59
|
+
const value = parentPath
|
|
85
60
|
? localConfig[parentPath]?.[fieldName] ?? field.default
|
|
86
61
|
: localConfig[fieldName] ?? field.default
|
|
87
62
|
|
|
@@ -91,148 +66,71 @@ export function PluginConfigForm({ pluginName, onSuccess }: Omit<PluginConfigFor
|
|
|
91
66
|
|
|
92
67
|
return (
|
|
93
68
|
<FieldRenderer
|
|
94
|
-
fieldName={fieldName}
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
onChange={
|
|
98
|
-
parentPath={parentPath}
|
|
99
|
-
onNestedChange={handleNestedFieldChange}
|
|
100
|
-
onArrayItemChange={handleArrayItemChange}
|
|
101
|
-
renderField={renderField}
|
|
102
|
-
renderNestedField={(fn, f, v, oc) => (
|
|
103
|
-
<NestedFieldRenderer fieldName={fn} field={f} value={v} onChange={oc} />
|
|
104
|
-
)}
|
|
69
|
+
fieldName={fieldName} field={field} value={value} onChange={onChange}
|
|
70
|
+
parentPath={parentPath} onNestedChange={handleNestedFieldChange}
|
|
71
|
+
onArrayItemChange={handleArrayItemChange} renderField={renderField}
|
|
72
|
+
renderNestedField={(fn, f, v, oc) => <NestedFieldRenderer fieldName={fn} field={f} value={v} onChange={oc} />}
|
|
105
73
|
/>
|
|
106
74
|
)
|
|
107
75
|
}
|
|
108
76
|
|
|
109
77
|
const fields = schema?.properties || schema?.dict || {}
|
|
110
|
-
|
|
111
|
-
if (!schema || !fields || Object.keys(fields).length === 0) {
|
|
112
|
-
return null // 没有配置则不显示
|
|
113
|
-
}
|
|
78
|
+
if (!schema || !fields || Object.keys(fields).length === 0) return null
|
|
114
79
|
|
|
115
80
|
return (
|
|
116
|
-
<Card
|
|
117
|
-
<Accordion
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
className="
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
{successMessage && (
|
|
149
|
-
<div className="mb-3 animate-in fade-in slide-in-from-top-2 duration-300">
|
|
150
|
-
<Callout.Root color="green" size="1" className="shadow-sm">
|
|
151
|
-
<Callout.Icon><CheckCircle /></Callout.Icon>
|
|
152
|
-
<Callout.Text className="font-medium">{successMessage}</Callout.Text>
|
|
153
|
-
</Callout.Root>
|
|
154
|
-
</div>
|
|
155
|
-
)}
|
|
156
|
-
|
|
157
|
-
{/* 错误提示 */}
|
|
158
|
-
{error && (
|
|
159
|
-
<div className="mb-3 animate-in fade-in slide-in-from-top-2 duration-300">
|
|
160
|
-
<Callout.Root color="red" size="1" className="shadow-sm">
|
|
161
|
-
<Callout.Icon><AlertCircle /></Callout.Icon>
|
|
162
|
-
<Callout.Text className="font-medium">{error}</Callout.Text>
|
|
163
|
-
</Callout.Root>
|
|
164
|
-
</div>
|
|
165
|
-
)}
|
|
166
|
-
|
|
167
|
-
{/* 配置表单 */}
|
|
168
|
-
<Flex direction="column" gap="3">
|
|
169
|
-
{Object.entries(fields).map(([fieldName, field]) => {
|
|
170
|
-
const schemaField = field as SchemaField
|
|
171
|
-
return (
|
|
172
|
-
<div
|
|
173
|
-
key={fieldName}
|
|
174
|
-
className="group p-3 rounded-lg bg-gray-50 dark:bg-gray-900/50 hover:bg-gray-100 dark:hover:bg-gray-900 transition-colors border border-gray-200 dark:border-gray-800"
|
|
175
|
-
>
|
|
176
|
-
<Flex direction="column" gap="2">
|
|
177
|
-
<Flex align="center" gap="1">
|
|
178
|
-
<Text size="2" weight="bold" className="text-gray-900 dark:text-gray-100">
|
|
179
|
-
{schemaField.key || fieldName}
|
|
180
|
-
</Text>
|
|
181
|
-
{schemaField.required && (
|
|
182
|
-
<Text size="2" weight="bold" color="red" className="leading-none">
|
|
183
|
-
*
|
|
184
|
-
</Text>
|
|
185
|
-
)}
|
|
186
|
-
</Flex>
|
|
187
|
-
{schemaField.description && (
|
|
188
|
-
<Text size="1" color="gray" className="leading-relaxed">
|
|
189
|
-
{schemaField.description}
|
|
190
|
-
</Text>
|
|
191
|
-
)}
|
|
192
|
-
<div className="mt-1">
|
|
193
|
-
{renderField(schemaField.key || fieldName, schemaField)}
|
|
194
|
-
</div>
|
|
195
|
-
</Flex>
|
|
81
|
+
<Card className="mt-4">
|
|
82
|
+
<Accordion type="single" collapsible value={isExpanded} onValueChange={setIsExpanded}>
|
|
83
|
+
<AccordionItem value="config" className="border-none">
|
|
84
|
+
<AccordionTrigger className="px-4 hover:no-underline">
|
|
85
|
+
<div className="flex items-center gap-2">
|
|
86
|
+
<Settings className="w-4 h-4" />
|
|
87
|
+
<span className="font-semibold">插件配置</span>
|
|
88
|
+
<Badge variant="secondary">{Object.keys(fields).length} 项</Badge>
|
|
89
|
+
</div>
|
|
90
|
+
</AccordionTrigger>
|
|
91
|
+
<AccordionContent className="px-4 pb-4">
|
|
92
|
+
{successMessage && (
|
|
93
|
+
<Alert variant="success" className="mb-3">
|
|
94
|
+
<CheckCircle className="h-4 w-4" />
|
|
95
|
+
<AlertDescription>{successMessage}</AlertDescription>
|
|
96
|
+
</Alert>
|
|
97
|
+
)}
|
|
98
|
+
{error && (
|
|
99
|
+
<Alert variant="destructive" className="mb-3">
|
|
100
|
+
<AlertCircle className="h-4 w-4" />
|
|
101
|
+
<AlertDescription>{error}</AlertDescription>
|
|
102
|
+
</Alert>
|
|
103
|
+
)}
|
|
104
|
+
|
|
105
|
+
<div className="space-y-3">
|
|
106
|
+
{Object.entries(fields).map(([fieldName, field]) => {
|
|
107
|
+
const schemaField = field as SchemaField
|
|
108
|
+
return (
|
|
109
|
+
<div key={fieldName} className="p-3 rounded-lg bg-muted/50 border space-y-2">
|
|
110
|
+
<div className="flex items-center gap-1">
|
|
111
|
+
<span className="text-sm font-semibold">{schemaField.key || fieldName}</span>
|
|
112
|
+
{schemaField.required && <span className="text-destructive font-bold">*</span>}
|
|
196
113
|
</div>
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
className="bg-blue-600 hover:bg-blue-700 dark:bg-blue-500 dark:hover:bg-blue-600 text-white transition-colors shadow-sm"
|
|
218
|
-
>
|
|
219
|
-
{loading ? (
|
|
220
|
-
<>
|
|
221
|
-
<Spinner />
|
|
222
|
-
<span>保存中...</span>
|
|
223
|
-
</>
|
|
224
|
-
) : (
|
|
225
|
-
<>
|
|
226
|
-
<Save className="w-4 h-4" />
|
|
227
|
-
<span>保存配置</span>
|
|
228
|
-
</>
|
|
229
|
-
)}
|
|
230
|
-
</Button>
|
|
231
|
-
</Flex>
|
|
232
|
-
</Box>
|
|
233
|
-
</Accordion.Content>
|
|
234
|
-
</Accordion.Item>
|
|
235
|
-
</Accordion.Root>
|
|
114
|
+
{schemaField.description && (
|
|
115
|
+
<p className="text-xs text-muted-foreground">{schemaField.description}</p>
|
|
116
|
+
)}
|
|
117
|
+
<div className="mt-1">{renderField(schemaField.key || fieldName, schemaField)}</div>
|
|
118
|
+
</div>
|
|
119
|
+
)
|
|
120
|
+
})}
|
|
121
|
+
</div>
|
|
122
|
+
|
|
123
|
+
<div className="flex gap-2 justify-end mt-4 pt-3 border-t">
|
|
124
|
+
<Button variant="outline" size="sm" onClick={() => setIsExpanded(undefined)} disabled={loading}>
|
|
125
|
+
<X className="w-4 h-4 mr-1" /> 取消
|
|
126
|
+
</Button>
|
|
127
|
+
<Button size="sm" onClick={handleSave} disabled={loading}>
|
|
128
|
+
{loading ? <><Loader2 className="w-4 h-4 mr-1 animate-spin" />保存中...</> : <><Save className="w-4 h-4 mr-1" />保存配置</>}
|
|
129
|
+
</Button>
|
|
130
|
+
</div>
|
|
131
|
+
</AccordionContent>
|
|
132
|
+
</AccordionItem>
|
|
133
|
+
</Accordion>
|
|
236
134
|
</Card>
|
|
237
135
|
)
|
|
238
136
|
}
|
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
import * as React from "react"
|
|
2
|
+
import { Accordion as AccordionPrimitive } from "radix-ui"
|
|
3
|
+
import { cn } from "@zhin.js/client"
|
|
4
|
+
import { ChevronDown } from "lucide-react"
|
|
5
|
+
|
|
6
|
+
const Accordion = AccordionPrimitive.Root
|
|
7
|
+
|
|
8
|
+
const AccordionItem = React.forwardRef<
|
|
9
|
+
React.ComponentRef<typeof AccordionPrimitive.Item>,
|
|
10
|
+
React.ComponentPropsWithoutRef<typeof AccordionPrimitive.Item>
|
|
11
|
+
>(({ className, ...props }, ref) => (
|
|
12
|
+
<AccordionPrimitive.Item
|
|
13
|
+
ref={ref}
|
|
14
|
+
className={cn("border-b", className)}
|
|
15
|
+
{...props}
|
|
16
|
+
/>
|
|
17
|
+
))
|
|
18
|
+
AccordionItem.displayName = "AccordionItem"
|
|
19
|
+
|
|
20
|
+
const AccordionTrigger = React.forwardRef<
|
|
21
|
+
React.ComponentRef<typeof AccordionPrimitive.Trigger>,
|
|
22
|
+
React.ComponentPropsWithoutRef<typeof AccordionPrimitive.Trigger>
|
|
23
|
+
>(({ className, children, ...props }, ref) => (
|
|
24
|
+
<AccordionPrimitive.Header className="flex">
|
|
25
|
+
<AccordionPrimitive.Trigger
|
|
26
|
+
ref={ref}
|
|
27
|
+
className={cn(
|
|
28
|
+
"flex flex-1 items-center justify-between py-4 text-sm font-medium transition-all hover:underline [&[data-state=open]>svg]:rotate-180",
|
|
29
|
+
className
|
|
30
|
+
)}
|
|
31
|
+
{...props}
|
|
32
|
+
>
|
|
33
|
+
{children}
|
|
34
|
+
<ChevronDown className="h-4 w-4 shrink-0 text-muted-foreground transition-transform duration-200" />
|
|
35
|
+
</AccordionPrimitive.Trigger>
|
|
36
|
+
</AccordionPrimitive.Header>
|
|
37
|
+
))
|
|
38
|
+
AccordionTrigger.displayName = "AccordionTrigger"
|
|
39
|
+
|
|
40
|
+
const AccordionContent = React.forwardRef<
|
|
41
|
+
React.ComponentRef<typeof AccordionPrimitive.Content>,
|
|
42
|
+
React.ComponentPropsWithoutRef<typeof AccordionPrimitive.Content>
|
|
43
|
+
>(({ className, children, ...props }, ref) => (
|
|
44
|
+
<AccordionPrimitive.Content
|
|
45
|
+
ref={ref}
|
|
46
|
+
className="overflow-hidden text-sm data-[state=closed]:animate-accordion-up data-[state=open]:animate-accordion-down"
|
|
47
|
+
{...props}
|
|
48
|
+
>
|
|
49
|
+
<div className={cn("pb-4 pt-0", className)}>{children}</div>
|
|
50
|
+
</AccordionPrimitive.Content>
|
|
51
|
+
))
|
|
52
|
+
AccordionContent.displayName = "AccordionContent"
|
|
53
|
+
|
|
54
|
+
export { Accordion, AccordionItem, AccordionTrigger, AccordionContent }
|
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
import * as React from "react"
|
|
2
|
+
import { cva, type VariantProps } from "class-variance-authority"
|
|
3
|
+
import { cn } from "@zhin.js/client"
|
|
4
|
+
|
|
5
|
+
const alertVariants = cva(
|
|
6
|
+
"relative w-full rounded-lg border px-4 py-3 text-sm [&>svg+div]:translate-y-[-3px] [&>svg]:absolute [&>svg]:left-4 [&>svg]:top-4 [&>svg]:text-foreground [&>svg~*]:pl-7",
|
|
7
|
+
{
|
|
8
|
+
variants: {
|
|
9
|
+
variant: {
|
|
10
|
+
default: "bg-background text-foreground",
|
|
11
|
+
destructive:
|
|
12
|
+
"border-destructive/50 text-destructive dark:border-destructive [&>svg]:text-destructive",
|
|
13
|
+
success:
|
|
14
|
+
"border-emerald-200 bg-emerald-50 text-emerald-800 dark:border-emerald-800 dark:bg-emerald-950/50 dark:text-emerald-400 [&>svg]:text-emerald-600 dark:[&>svg]:text-emerald-400",
|
|
15
|
+
warning:
|
|
16
|
+
"border-amber-200 bg-amber-50 text-amber-800 dark:border-amber-800 dark:bg-amber-950/50 dark:text-amber-400 [&>svg]:text-amber-600 dark:[&>svg]:text-amber-400",
|
|
17
|
+
},
|
|
18
|
+
},
|
|
19
|
+
defaultVariants: {
|
|
20
|
+
variant: "default",
|
|
21
|
+
},
|
|
22
|
+
}
|
|
23
|
+
)
|
|
24
|
+
|
|
25
|
+
const Alert = React.forwardRef<
|
|
26
|
+
HTMLDivElement,
|
|
27
|
+
React.HTMLAttributes<HTMLDivElement> & VariantProps<typeof alertVariants>
|
|
28
|
+
>(({ className, variant, ...props }, ref) => (
|
|
29
|
+
<div
|
|
30
|
+
ref={ref}
|
|
31
|
+
role="alert"
|
|
32
|
+
className={cn(alertVariants({ variant }), className)}
|
|
33
|
+
{...props}
|
|
34
|
+
/>
|
|
35
|
+
))
|
|
36
|
+
Alert.displayName = "Alert"
|
|
37
|
+
|
|
38
|
+
const AlertTitle = React.forwardRef<
|
|
39
|
+
HTMLParagraphElement,
|
|
40
|
+
React.HTMLAttributes<HTMLHeadingElement>
|
|
41
|
+
>(({ className, ...props }, ref) => (
|
|
42
|
+
<h5
|
|
43
|
+
ref={ref}
|
|
44
|
+
className={cn("mb-1 font-medium leading-none tracking-tight", className)}
|
|
45
|
+
{...props}
|
|
46
|
+
/>
|
|
47
|
+
))
|
|
48
|
+
AlertTitle.displayName = "AlertTitle"
|
|
49
|
+
|
|
50
|
+
const AlertDescription = React.forwardRef<
|
|
51
|
+
HTMLParagraphElement,
|
|
52
|
+
React.HTMLAttributes<HTMLParagraphElement>
|
|
53
|
+
>(({ className, ...props }, ref) => (
|
|
54
|
+
<div
|
|
55
|
+
ref={ref}
|
|
56
|
+
className={cn("text-sm [&_p]:leading-relaxed", className)}
|
|
57
|
+
{...props}
|
|
58
|
+
/>
|
|
59
|
+
))
|
|
60
|
+
AlertDescription.displayName = "AlertDescription"
|
|
61
|
+
|
|
62
|
+
export { Alert, AlertTitle, AlertDescription }
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
import * as React from "react"
|
|
2
|
+
import { Avatar as AvatarPrimitive } from "radix-ui"
|
|
3
|
+
import { cn } from "@zhin.js/client"
|
|
4
|
+
|
|
5
|
+
const Avatar = React.forwardRef<
|
|
6
|
+
React.ComponentRef<typeof AvatarPrimitive.Root>,
|
|
7
|
+
React.ComponentPropsWithoutRef<typeof AvatarPrimitive.Root>
|
|
8
|
+
>(({ className, ...props }, ref) => (
|
|
9
|
+
<AvatarPrimitive.Root
|
|
10
|
+
ref={ref}
|
|
11
|
+
className={cn("relative flex h-10 w-10 shrink-0 overflow-hidden rounded-full", className)}
|
|
12
|
+
{...props}
|
|
13
|
+
/>
|
|
14
|
+
))
|
|
15
|
+
Avatar.displayName = "Avatar"
|
|
16
|
+
|
|
17
|
+
const AvatarImage = React.forwardRef<
|
|
18
|
+
React.ComponentRef<typeof AvatarPrimitive.Image>,
|
|
19
|
+
React.ComponentPropsWithoutRef<typeof AvatarPrimitive.Image>
|
|
20
|
+
>(({ className, ...props }, ref) => (
|
|
21
|
+
<AvatarPrimitive.Image
|
|
22
|
+
ref={ref}
|
|
23
|
+
className={cn("aspect-square h-full w-full", className)}
|
|
24
|
+
{...props}
|
|
25
|
+
/>
|
|
26
|
+
))
|
|
27
|
+
AvatarImage.displayName = "AvatarImage"
|
|
28
|
+
|
|
29
|
+
const AvatarFallback = React.forwardRef<
|
|
30
|
+
React.ComponentRef<typeof AvatarPrimitive.Fallback>,
|
|
31
|
+
React.ComponentPropsWithoutRef<typeof AvatarPrimitive.Fallback>
|
|
32
|
+
>(({ className, ...props }, ref) => (
|
|
33
|
+
<AvatarPrimitive.Fallback
|
|
34
|
+
ref={ref}
|
|
35
|
+
className={cn("flex h-full w-full items-center justify-center rounded-full bg-muted", className)}
|
|
36
|
+
{...props}
|
|
37
|
+
/>
|
|
38
|
+
))
|
|
39
|
+
AvatarFallback.displayName = "AvatarFallback"
|
|
40
|
+
|
|
41
|
+
export { Avatar, AvatarImage, AvatarFallback }
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
import * as React from "react"
|
|
2
|
+
import { cva, type VariantProps } from "class-variance-authority"
|
|
3
|
+
import { cn } from "@zhin.js/client"
|
|
4
|
+
|
|
5
|
+
const badgeVariants = cva(
|
|
6
|
+
"inline-flex items-center rounded-full border px-2.5 py-0.5 text-xs font-semibold transition-colors focus:outline-none focus:ring-2 focus:ring-ring focus:ring-offset-2",
|
|
7
|
+
{
|
|
8
|
+
variants: {
|
|
9
|
+
variant: {
|
|
10
|
+
default: "border-transparent bg-primary text-primary-foreground shadow",
|
|
11
|
+
secondary: "border-transparent bg-secondary text-secondary-foreground",
|
|
12
|
+
destructive: "border-transparent bg-destructive text-destructive-foreground shadow",
|
|
13
|
+
outline: "text-foreground",
|
|
14
|
+
success: "border-transparent bg-emerald-100 text-emerald-800 dark:bg-emerald-900/30 dark:text-emerald-400",
|
|
15
|
+
warning: "border-transparent bg-amber-100 text-amber-800 dark:bg-amber-900/30 dark:text-amber-400",
|
|
16
|
+
},
|
|
17
|
+
},
|
|
18
|
+
defaultVariants: {
|
|
19
|
+
variant: "default",
|
|
20
|
+
},
|
|
21
|
+
}
|
|
22
|
+
)
|
|
23
|
+
|
|
24
|
+
export interface BadgeProps
|
|
25
|
+
extends React.HTMLAttributes<HTMLDivElement>,
|
|
26
|
+
VariantProps<typeof badgeVariants> {}
|
|
27
|
+
|
|
28
|
+
function Badge({ className, variant, ...props }: BadgeProps) {
|
|
29
|
+
return <div className={cn(badgeVariants({ variant }), className)} {...props} />
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
export { Badge, badgeVariants }
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
import * as React from "react"
|
|
2
|
+
import { cva, type VariantProps } from "class-variance-authority"
|
|
3
|
+
import { cn } from "@zhin.js/client"
|
|
4
|
+
|
|
5
|
+
const buttonVariants = cva(
|
|
6
|
+
"inline-flex items-center justify-center gap-2 whitespace-nowrap rounded-md text-sm font-medium transition-colors focus-visible:outline-none focus-visible:ring-1 focus-visible:ring-ring disabled:pointer-events-none disabled:opacity-50 [&_svg]:pointer-events-none [&_svg]:size-4 [&_svg]:shrink-0",
|
|
7
|
+
{
|
|
8
|
+
variants: {
|
|
9
|
+
variant: {
|
|
10
|
+
default: "bg-primary text-primary-foreground shadow hover:bg-primary/90",
|
|
11
|
+
destructive: "bg-destructive text-destructive-foreground shadow-sm hover:bg-destructive/90",
|
|
12
|
+
outline: "border border-input bg-background shadow-sm hover:bg-accent hover:text-accent-foreground",
|
|
13
|
+
secondary: "bg-secondary text-secondary-foreground shadow-sm hover:bg-secondary/80",
|
|
14
|
+
ghost: "hover:bg-accent hover:text-accent-foreground",
|
|
15
|
+
link: "text-primary underline-offset-4 hover:underline",
|
|
16
|
+
},
|
|
17
|
+
size: {
|
|
18
|
+
default: "h-9 px-4 py-2",
|
|
19
|
+
sm: "h-8 rounded-md px-3 text-xs",
|
|
20
|
+
lg: "h-10 rounded-md px-8",
|
|
21
|
+
icon: "h-9 w-9",
|
|
22
|
+
},
|
|
23
|
+
},
|
|
24
|
+
defaultVariants: {
|
|
25
|
+
variant: "default",
|
|
26
|
+
size: "default",
|
|
27
|
+
},
|
|
28
|
+
}
|
|
29
|
+
)
|
|
30
|
+
|
|
31
|
+
export interface ButtonProps
|
|
32
|
+
extends React.ButtonHTMLAttributes<HTMLButtonElement>,
|
|
33
|
+
VariantProps<typeof buttonVariants> {
|
|
34
|
+
asChild?: boolean
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
const Button = React.forwardRef<HTMLButtonElement, ButtonProps>(
|
|
38
|
+
({ className, variant, size, ...props }, ref) => {
|
|
39
|
+
return (
|
|
40
|
+
<button
|
|
41
|
+
className={cn(buttonVariants({ variant, size, className }))}
|
|
42
|
+
ref={ref}
|
|
43
|
+
{...props}
|
|
44
|
+
/>
|
|
45
|
+
)
|
|
46
|
+
}
|
|
47
|
+
)
|
|
48
|
+
Button.displayName = "Button"
|
|
49
|
+
|
|
50
|
+
export { Button, buttonVariants }
|