@zhin.js/console 1.0.21 → 1.0.23

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 (56) hide show
  1. package/CHANGELOG.md +20 -0
  2. package/README.md +4 -4
  3. package/client/components.json +17 -0
  4. package/client/index.html +1 -1
  5. package/client/src/components/PluginConfigForm/BasicFieldRenderers.tsx +89 -180
  6. package/client/src/components/PluginConfigForm/CollectionFieldRenderers.tsx +97 -200
  7. package/client/src/components/PluginConfigForm/CompositeFieldRenderers.tsx +31 -70
  8. package/client/src/components/PluginConfigForm/FieldRenderer.tsx +27 -77
  9. package/client/src/components/PluginConfigForm/NestedFieldRenderer.tsx +33 -53
  10. package/client/src/components/PluginConfigForm/index.tsx +71 -173
  11. package/client/src/components/ui/accordion.tsx +54 -0
  12. package/client/src/components/ui/alert.tsx +62 -0
  13. package/client/src/components/ui/avatar.tsx +41 -0
  14. package/client/src/components/ui/badge.tsx +32 -0
  15. package/client/src/components/ui/button.tsx +50 -0
  16. package/client/src/components/ui/card.tsx +50 -0
  17. package/client/src/components/ui/checkbox.tsx +25 -0
  18. package/client/src/components/ui/dialog.tsx +87 -0
  19. package/client/src/components/ui/dropdown-menu.tsx +97 -0
  20. package/client/src/components/ui/input.tsx +21 -0
  21. package/client/src/components/ui/scroll-area.tsx +43 -0
  22. package/client/src/components/ui/select.tsx +127 -0
  23. package/client/src/components/ui/separator.tsx +23 -0
  24. package/client/src/components/ui/skeleton.tsx +12 -0
  25. package/client/src/components/ui/switch.tsx +26 -0
  26. package/client/src/components/ui/tabs.tsx +52 -0
  27. package/client/src/components/ui/textarea.tsx +20 -0
  28. package/client/src/components/ui/tooltip.tsx +27 -0
  29. package/client/src/layouts/dashboard.tsx +91 -221
  30. package/client/src/main.tsx +38 -42
  31. package/client/src/pages/dashboard-bots.tsx +91 -137
  32. package/client/src/pages/dashboard-home.tsx +133 -204
  33. package/client/src/pages/dashboard-logs.tsx +125 -196
  34. package/client/src/pages/dashboard-plugin-detail.tsx +261 -329
  35. package/client/src/pages/dashboard-plugins.tsx +108 -105
  36. package/client/src/style.css +156 -865
  37. package/client/src/theme/index.ts +60 -35
  38. package/client/tailwind.config.js +78 -69
  39. package/dist/client.js +1 -1
  40. package/dist/cva.js +47 -0
  41. package/dist/index.html +1 -1
  42. package/dist/index.js +6 -6
  43. package/dist/react-router.js +7121 -5585
  44. package/dist/react.js +192 -149
  45. package/dist/style.css +2 -2
  46. package/lib/bin.js +2 -2
  47. package/lib/build.js +2 -2
  48. package/lib/index.d.ts +0 -3
  49. package/lib/index.js +160 -205
  50. package/lib/transform.d.ts +26 -0
  51. package/lib/transform.js +78 -0
  52. package/lib/websocket.d.ts +0 -1
  53. package/package.json +9 -8
  54. package/dist/radix-ui-themes.js +0 -9305
  55. package/lib/dev.d.ts +0 -18
  56. 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
- <TextField.Root
21
- size="1"
22
- value={value || ''}
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
- <TextField.Root
32
- size="1"
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
- <Flex align="center" gap="2">
45
- <Switch
46
- checked={value === true}
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
- </Text>
52
- </Flex>
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 size="1">
59
- <Flex direction="column" gap="2" p="2">
60
- {Object.entries(objectFields).map(([key, nestedField]: [string, any]) => (
61
- <Box key={key}>
62
- <Text size="1" weight="bold">{key}</Text>
63
- <NestedFieldRenderer
64
- fieldName={key}
65
- field={nestedField}
66
- value={value?.[key]}
67
- onChange={(val) => {
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
- <TextArea
81
- size="1"
68
+ <Textarea
82
69
  value={typeof value === 'object' ? JSON.stringify(value, null, 2) : value || ''}
83
- onChange={(e) => {
84
- try {
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, Schema } from './types.js'
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 fullPath = parentPath ? `${parentPath}.${fieldName}` : fieldName
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
- field={field}
96
- value={value}
97
- onChange={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 size="2" className="mt-4">
117
- <Accordion.Root
118
- type="single"
119
- collapsible
120
- value={isExpanded}
121
- onValueChange={setIsExpanded}
122
- >
123
- <Accordion.Item value="config" className="border-none">
124
- <Accordion.Header>
125
- <Accordion.Trigger className="w-full group">
126
- <Flex
127
- justify="between"
128
- align="center"
129
- className="w-full p-3 hover:bg-gray-50 dark:hover:bg-gray-900/50 rounded-lg transition-colors"
130
- >
131
- <Flex align="center" gap="2">
132
- <Settings className="w-5 h-5 text-blue-600 dark:text-blue-400" />
133
- <Text size="3" weight="bold">插件配置</Text>
134
- <Badge size="1" color="gray" variant="soft">
135
- {Object.keys(fields).length}
136
- </Badge>
137
- </Flex>
138
- <ChevronDown
139
- className="w-5 h-5 text-gray-500 transition-transform duration-200 group-data-[state=open]:rotate-180"
140
- />
141
- </Flex>
142
- </Accordion.Trigger>
143
- </Accordion.Header>
144
-
145
- <Accordion.Content className="overflow-hidden data-[state=open]:animate-accordion-down data-[state=closed]:animate-accordion-up">
146
- <Box className="pt-2 pb-4">
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
- </Flex>
200
-
201
- {/* 操作按钮 */}
202
- <Flex gap="2" justify="end" className="mt-4 pt-3 border-t border-gray-200 dark:border-gray-800">
203
- <Button
204
- size="2"
205
- variant="soft"
206
- onClick={() => setIsExpanded(undefined)}
207
- disabled={loading}
208
- className="hover:bg-gray-200 dark:hover:bg-gray-800 transition-colors"
209
- >
210
- <X className="w-4 h-4" />
211
- 取消
212
- </Button>
213
- <Button
214
- size="2"
215
- onClick={handleSave}
216
- disabled={loading}
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 }