@hanzo/ui 4.6.0 → 4.8.2

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 (184) hide show
  1. package/assets/general.tsx +1 -1
  2. package/assets/hanzo-logo.tsx +3 -1
  3. package/assets/index.ts +119 -5
  4. package/blocks/auth/index.ts +6 -0
  5. package/blocks/auth/login-2fa.tsx +165 -0
  6. package/blocks/auth/login-basic.tsx +94 -0
  7. package/blocks/auth/login-social.tsx +148 -0
  8. package/blocks/auth/magic-link.tsx +129 -0
  9. package/blocks/auth/password-reset.tsx +97 -0
  10. package/blocks/auth/signup.tsx +157 -0
  11. package/blocks/data-display/activity-feed.tsx +242 -0
  12. package/blocks/data-display/data-table.tsx +235 -0
  13. package/blocks/data-display/stats-grid.tsx +194 -0
  14. package/blocks/ecommerce/checkout.tsx +242 -0
  15. package/blocks/ecommerce/index.ts +7 -0
  16. package/blocks/ecommerce/product-detail.tsx +257 -0
  17. package/blocks/ecommerce/product-grid.tsx +148 -0
  18. package/blocks/ecommerce/shopping-cart.tsx +181 -0
  19. package/blocks/marketing/cta-section.tsx +207 -0
  20. package/blocks/marketing/faq.tsx +159 -0
  21. package/blocks/marketing/features-grid.tsx +156 -0
  22. package/blocks/marketing/hero-section.tsx +192 -0
  23. package/blocks/marketing/index.ts +6 -0
  24. package/blocks/marketing/pricing-table.tsx +121 -0
  25. package/blocks/marketing/testimonials.tsx +196 -0
  26. package/components/index.ts +4 -51
  27. package/dist/index.js +9351 -0
  28. package/dist/index.mjs +9340 -0
  29. package/dist/lib/utils.js +47 -0
  30. package/dist/lib/utils.mjs +28 -0
  31. package/dist/src/utils.js +47 -0
  32. package/dist/src/utils.mjs +28 -0
  33. package/dist/tailwind/index.js +2050 -0
  34. package/dist/tailwind/index.mjs +2019 -0
  35. package/dist/types/index.js +79 -0
  36. package/dist/types/index.mjs +56 -0
  37. package/dist/util/format-text.js +51 -0
  38. package/dist/util/format-text.mjs +32 -0
  39. package/dist/util/index.js +384 -0
  40. package/dist/util/index.mjs +363 -0
  41. package/frameworks/core/index.ts +6 -0
  42. package/frameworks/core/utils/index.ts +64 -0
  43. package/frameworks/react/components/button.tsx +26 -0
  44. package/frameworks/react/components/index.ts +5 -0
  45. package/frameworks/react/hooks/index.ts +5 -0
  46. package/frameworks/react/index.ts +9 -0
  47. package/frameworks/react/package.json +8 -0
  48. package/frameworks/react/utils/index.ts +2 -0
  49. package/frameworks/react-native/index.ts +9 -0
  50. package/frameworks/react-native/package.json +8 -0
  51. package/frameworks/registry.json +371 -0
  52. package/frameworks/setup.sh +69 -0
  53. package/frameworks/svelte/index.ts +9 -0
  54. package/frameworks/svelte/package.json +8 -0
  55. package/frameworks/tracker.json +1854 -0
  56. package/frameworks/vue/index.ts +9 -0
  57. package/frameworks/vue/package.json +8 -0
  58. package/package.json +192 -28
  59. package/primitives/accordion.tsx +1 -1
  60. package/primitives/alert-dialog.tsx +1 -1
  61. package/primitives/alert.tsx +1 -1
  62. package/primitives/avatar.tsx +1 -1
  63. package/primitives/badge.tsx +2 -1
  64. package/primitives/breadcrumb.tsx +1 -1
  65. package/primitives/button.tsx +37 -47
  66. package/primitives/card.tsx +1 -1
  67. package/primitives/carousel.tsx +3 -2
  68. package/primitives/chat/chat-input-area.tsx +5 -4
  69. package/primitives/chat/chat-input.tsx +2 -2
  70. package/primitives/chat/files-preview.tsx +5 -4
  71. package/primitives/chat/message-list.tsx +2 -1
  72. package/primitives/chat/sqlite-preview.tsx +8 -8
  73. package/primitives/checkbox.tsx +2 -1
  74. package/primitives/command.tsx +3 -1
  75. package/primitives/context-menu.tsx +1 -1
  76. package/primitives/dialog.tsx +6 -1
  77. package/primitives/drawer.tsx +4 -1
  78. package/primitives/dropdown-menu.tsx +1 -1
  79. package/primitives/file-uploader.tsx +4 -2
  80. package/primitives/form.tsx +1 -1
  81. package/primitives/hover-card.tsx +1 -1
  82. package/primitives/icons/github.tsx +2 -2
  83. package/primitives/icons/youtube-logo.tsx +1 -1
  84. package/primitives/index-common.ts +7 -6
  85. package/primitives/input-otp.tsx +1 -1
  86. package/primitives/input.tsx +2 -1
  87. package/primitives/label.tsx +2 -1
  88. package/primitives/markdown-preview.tsx +3 -0
  89. package/primitives/mermaid.tsx +13 -18
  90. package/primitives/next/image.tsx +2 -1
  91. package/primitives/next/inline-icon.tsx +14 -14
  92. package/primitives/next/media-stack.tsx +2 -19
  93. package/primitives/pagination.tsx +1 -1
  94. package/primitives/popover.tsx +4 -2
  95. package/primitives/progress.tsx +2 -1
  96. package/primitives/prompt-textarea.tsx +1 -1
  97. package/primitives/radio-group.tsx +1 -1
  98. package/primitives/scroll-area.tsx +1 -1
  99. package/primitives/search-input.tsx +1 -1
  100. package/primitives/select.tsx +1 -1
  101. package/primitives/separator.tsx +2 -1
  102. package/primitives/sheet.tsx +1 -1
  103. package/primitives/skeleton.tsx +1 -0
  104. package/primitives/slider.tsx +2 -1
  105. package/primitives/stepper.tsx +1 -1
  106. package/primitives/switch.tsx +2 -1
  107. package/primitives/table.tsx +1 -1
  108. package/primitives/tabs.tsx +1 -1
  109. package/primitives/textarea.tsx +2 -1
  110. package/primitives/textfield.tsx +1 -0
  111. package/primitives/toggle-group.tsx +1 -1
  112. package/primitives/toggle.tsx +1 -1
  113. package/primitives/tooltip.tsx +1 -1
  114. package/src/hooks/use-copy-clipboard.ts +1 -1
  115. package/src/index-lean.ts +87 -0
  116. package/src/index.ts +54 -0
  117. package/src/registry/api.ts +1 -1
  118. package/src/utils.ts +19 -1
  119. package/tailwind/tailwind.config.hanzo-preset.js +7 -7
  120. package/tailwind/typo-plugin/index.js +1 -1
  121. package/types/animation-def.ts +1 -1
  122. package/types/index.ts +2 -1
  123. package/util/blob.ts +9 -4
  124. package/util/date.ts +2 -1
  125. package/util/format-text.ts +2 -1
  126. package/util/index.ts +103 -0
  127. package/util/spread-to-transform.ts +9 -8
  128. package/MCP-INSTRUCTIONS.md +0 -73
  129. package/README-MCP.md +0 -175
  130. package/dist/button.d.ts +0 -1
  131. package/dist/button.js +0 -1
  132. package/dist/hooks/index.d.ts +0 -7
  133. package/dist/hooks/index.js +0 -7
  134. package/dist/hooks/use-click-away.d.ts +0 -2
  135. package/dist/hooks/use-click-away.js +0 -23
  136. package/dist/hooks/use-combined-refs.d.ts +0 -3
  137. package/dist/hooks/use-combined-refs.js +0 -18
  138. package/dist/hooks/use-copy-clipboard.d.ts +0 -9
  139. package/dist/hooks/use-copy-clipboard.js +0 -21
  140. package/dist/hooks/use-debounce.d.ts +0 -1
  141. package/dist/hooks/use-debounce.js +0 -13
  142. package/dist/hooks/use-fill-ids.d.ts +0 -8
  143. package/dist/hooks/use-fill-ids.js +0 -20
  144. package/dist/hooks/use-map.d.ts +0 -1
  145. package/dist/hooks/use-map.js +0 -20
  146. package/dist/hooks/use-measure.d.ts +0 -8
  147. package/dist/hooks/use-measure.js +0 -25
  148. package/dist/hooks/use-reverse-video-playback.d.ts +0 -1
  149. package/dist/hooks/use-reverse-video-playback.js +0 -41
  150. package/dist/hooks/use-scroll-restoration.d.ts +0 -8
  151. package/dist/hooks/use-scroll-restoration.js +0 -36
  152. package/dist/mcp/enhanced-server.d.ts +0 -29
  153. package/dist/mcp/enhanced-server.js +0 -1128
  154. package/dist/mcp/index.d.ts +0 -28
  155. package/dist/mcp/index.js +0 -436
  156. package/dist/registry/api.d.ts +0 -37
  157. package/dist/registry/api.js +0 -129
  158. package/dist/registry/index.d.ts +0 -353
  159. package/dist/registry/index.js +0 -45
  160. package/dist/utils.d.ts +0 -1
  161. package/dist/utils.js +0 -1
  162. package/environment.d.ts +0 -6
  163. package/public/r/accordion.json +0 -11
  164. package/public/r/alert.json +0 -11
  165. package/public/r/avatar.json +0 -11
  166. package/public/r/badge.json +0 -11
  167. package/public/r/button.json +0 -11
  168. package/public/r/card.json +0 -11
  169. package/public/r/checkbox.json +0 -11
  170. package/public/r/default.json +0 -6
  171. package/public/r/dialog.json +0 -11
  172. package/public/r/input.json +0 -11
  173. package/public/r/label.json +0 -11
  174. package/public/r/new-york.json +0 -6
  175. package/public/r/popover.json +0 -11
  176. package/public/r/select.json +0 -11
  177. package/public/r/table.json +0 -11
  178. package/public/r/tabs.json +0 -11
  179. package/public/r/toast.json +0 -11
  180. package/registry.json +0 -184
  181. package/test/test-registry.js +0 -73
  182. package/test-imports.mjs +0 -19
  183. package/tsconfig.json +0 -22
  184. package/utils.ts +0 -9
@@ -0,0 +1,97 @@
1
+ 'use client'
2
+
3
+ import { cn } from '@hanzo/ui/util'
4
+ import { Button } from '@hanzo/ui/primitives'
5
+ import {
6
+ Card,
7
+ CardContent,
8
+ CardDescription,
9
+ CardHeader,
10
+ CardTitle,
11
+ } from '@hanzo/ui/primitives'
12
+ import { Input } from '@hanzo/ui/primitives'
13
+ import { Label } from '@hanzo/ui/primitives'
14
+
15
+ interface PasswordResetProps extends React.ComponentPropsWithoutRef<'div'> {
16
+ onSubmit?: (email: string) => void
17
+ onBackToLogin?: () => void
18
+ sent?: boolean
19
+ }
20
+
21
+ export function PasswordReset({
22
+ className,
23
+ onSubmit,
24
+ onBackToLogin,
25
+ sent = false,
26
+ ...props
27
+ }: PasswordResetProps) {
28
+ const handleSubmit = (e: React.FormEvent<HTMLFormElement>) => {
29
+ e.preventDefault()
30
+ const formData = new FormData(e.currentTarget)
31
+ const email = formData.get('email') as string
32
+ onSubmit?.(email)
33
+ }
34
+
35
+ return (
36
+ <div className={cn('flex flex-col gap-6', className)} {...props}>
37
+ <Card>
38
+ <CardHeader>
39
+ <CardTitle className="text-2xl">Reset password</CardTitle>
40
+ <CardDescription>
41
+ {sent
42
+ ? 'Check your email for a password reset link'
43
+ : 'Enter your email address and we will send you a password reset link'}
44
+ </CardDescription>
45
+ </CardHeader>
46
+ <CardContent>
47
+ {sent ? (
48
+ <div className="flex flex-col gap-6">
49
+ <div className="text-center text-sm text-muted-foreground">
50
+ We&apos;ve sent a password reset link to your email address.
51
+ Please check your inbox and follow the instructions to reset your password.
52
+ </div>
53
+ {onBackToLogin && (
54
+ <Button
55
+ type="button"
56
+ variant="outline"
57
+ className="w-full"
58
+ onClick={onBackToLogin}
59
+ >
60
+ Back to login
61
+ </Button>
62
+ )}
63
+ </div>
64
+ ) : (
65
+ <form onSubmit={handleSubmit}>
66
+ <div className="flex flex-col gap-6">
67
+ <div className="grid gap-2">
68
+ <Label htmlFor="email">Email</Label>
69
+ <Input
70
+ id="email"
71
+ name="email"
72
+ type="email"
73
+ placeholder="m@example.com"
74
+ required
75
+ />
76
+ </div>
77
+ <Button type="submit" className="w-full">
78
+ Send reset link
79
+ </Button>
80
+ {onBackToLogin && (
81
+ <Button
82
+ type="button"
83
+ variant="outline"
84
+ className="w-full"
85
+ onClick={onBackToLogin}
86
+ >
87
+ Back to login
88
+ </Button>
89
+ )}
90
+ </div>
91
+ </form>
92
+ )}
93
+ </CardContent>
94
+ </Card>
95
+ </div>
96
+ )
97
+ }
@@ -0,0 +1,157 @@
1
+ 'use client'
2
+
3
+ import { cn } from '@hanzo/ui/util'
4
+ import { Button } from '@hanzo/ui/primitives'
5
+ import {
6
+ Card,
7
+ CardContent,
8
+ CardDescription,
9
+ CardHeader,
10
+ CardTitle,
11
+ } from '@hanzo/ui/primitives'
12
+ import { Input } from '@hanzo/ui/primitives'
13
+ import { Label } from '@hanzo/ui/primitives'
14
+ import { Checkbox } from '@hanzo/ui/primitives'
15
+
16
+ interface SignupProps extends React.ComponentPropsWithoutRef<'div'> {
17
+ onSubmit?: (data: {
18
+ firstName: string
19
+ lastName: string
20
+ email: string
21
+ password: string
22
+ acceptTerms: boolean
23
+ }) => void
24
+ onLogin?: () => void
25
+ onGoogleSignup?: () => void
26
+ }
27
+
28
+ export function Signup({
29
+ className,
30
+ onSubmit,
31
+ onLogin,
32
+ onGoogleSignup,
33
+ ...props
34
+ }: SignupProps) {
35
+ const handleSubmit = (e: React.FormEvent<HTMLFormElement>) => {
36
+ e.preventDefault()
37
+ const formData = new FormData(e.currentTarget)
38
+ const data = {
39
+ firstName: formData.get('firstName') as string,
40
+ lastName: formData.get('lastName') as string,
41
+ email: formData.get('email') as string,
42
+ password: formData.get('password') as string,
43
+ acceptTerms: formData.get('acceptTerms') === 'on',
44
+ }
45
+ onSubmit?.(data)
46
+ }
47
+
48
+ return (
49
+ <div className={cn('flex flex-col gap-6', className)} {...props}>
50
+ <Card>
51
+ <CardHeader>
52
+ <CardTitle className="text-2xl">Sign Up</CardTitle>
53
+ <CardDescription>
54
+ Create a new account to get started
55
+ </CardDescription>
56
+ </CardHeader>
57
+ <CardContent>
58
+ <form onSubmit={handleSubmit}>
59
+ <div className="flex flex-col gap-6">
60
+ <div className="grid gap-4 sm:grid-cols-2">
61
+ <div className="grid gap-2">
62
+ <Label htmlFor="firstName">First name</Label>
63
+ <Input
64
+ id="firstName"
65
+ name="firstName"
66
+ placeholder="Max"
67
+ required
68
+ />
69
+ </div>
70
+ <div className="grid gap-2">
71
+ <Label htmlFor="lastName">Last name</Label>
72
+ <Input
73
+ id="lastName"
74
+ name="lastName"
75
+ placeholder="Robinson"
76
+ required
77
+ />
78
+ </div>
79
+ </div>
80
+ <div className="grid gap-2">
81
+ <Label htmlFor="email">Email</Label>
82
+ <Input
83
+ id="email"
84
+ name="email"
85
+ type="email"
86
+ placeholder="m@example.com"
87
+ required
88
+ />
89
+ </div>
90
+ <div className="grid gap-2">
91
+ <Label htmlFor="password">Password</Label>
92
+ <Input
93
+ id="password"
94
+ name="password"
95
+ type="password"
96
+ required
97
+ />
98
+ <p className="text-xs text-muted-foreground">
99
+ Must be at least 8 characters
100
+ </p>
101
+ </div>
102
+ <div className="flex items-center space-x-2">
103
+ <Checkbox id="acceptTerms" name="acceptTerms" required />
104
+ <label
105
+ htmlFor="acceptTerms"
106
+ className="text-sm font-medium leading-none peer-disabled:cursor-not-allowed peer-disabled:opacity-70"
107
+ >
108
+ I accept the{' '}
109
+ <a href="#" className="underline underline-offset-4">
110
+ terms and conditions
111
+ </a>
112
+ </label>
113
+ </div>
114
+ <Button type="submit" className="w-full">
115
+ Create account
116
+ </Button>
117
+ {onGoogleSignup && (
118
+ <>
119
+ <div className="relative">
120
+ <div className="absolute inset-0 flex items-center">
121
+ <span className="w-full border-t" />
122
+ </div>
123
+ <div className="relative flex justify-center text-xs uppercase">
124
+ <span className="bg-background px-2 text-muted-foreground">
125
+ Or continue with
126
+ </span>
127
+ </div>
128
+ </div>
129
+ <Button
130
+ type="button"
131
+ variant="outline"
132
+ className="w-full"
133
+ onClick={onGoogleSignup}
134
+ >
135
+ Sign up with Google
136
+ </Button>
137
+ </>
138
+ )}
139
+ </div>
140
+ {onLogin && (
141
+ <div className="mt-4 text-center text-sm">
142
+ Already have an account?{' '}
143
+ <button
144
+ type="button"
145
+ onClick={onLogin}
146
+ className="underline underline-offset-4"
147
+ >
148
+ Login
149
+ </button>
150
+ </div>
151
+ )}
152
+ </form>
153
+ </CardContent>
154
+ </Card>
155
+ </div>
156
+ )
157
+ }
@@ -0,0 +1,242 @@
1
+ 'use client'
2
+
3
+ import { cn } from '@hanzo/ui/util'
4
+ import { Avatar, AvatarFallback, AvatarImage } from '@hanzo/ui/primitives'
5
+ import { Badge } from '@hanzo/ui/primitives'
6
+ import { Card, CardContent, CardHeader, CardTitle } from '@hanzo/ui/primitives'
7
+ import type { LucideIcon } from 'lucide-react'
8
+
9
+ interface Activity {
10
+ id: string | number
11
+ user: {
12
+ name: string
13
+ avatar?: string
14
+ email?: string
15
+ }
16
+ action: string
17
+ target?: string
18
+ timestamp: Date | string
19
+ type?: 'default' | 'success' | 'warning' | 'error' | 'info'
20
+ icon?: LucideIcon
21
+ metadata?: Record<string, any>
22
+ }
23
+
24
+ interface ActivityFeedProps extends React.ComponentPropsWithoutRef<'div'> {
25
+ activities: Activity[]
26
+ title?: string
27
+ variant?: 'default' | 'timeline' | 'compact'
28
+ showDate?: boolean
29
+ }
30
+
31
+ export function ActivityFeed({
32
+ className,
33
+ activities,
34
+ title,
35
+ variant = 'default',
36
+ showDate = true,
37
+ ...props
38
+ }: ActivityFeedProps) {
39
+ const formatTime = (timestamp: Date | string) => {
40
+ const date = new Date(timestamp)
41
+ const now = new Date()
42
+ const diff = now.getTime() - date.getTime()
43
+
44
+ const minutes = Math.floor(diff / 60000)
45
+ const hours = Math.floor(diff / 3600000)
46
+ const days = Math.floor(diff / 86400000)
47
+
48
+ if (minutes < 1) return 'Just now'
49
+ if (minutes < 60) return `${minutes} minute${minutes > 1 ? 's' : ''} ago`
50
+ if (hours < 24) return `${hours} hour${hours > 1 ? 's' : ''} ago`
51
+ if (days < 7) return `${days} day${days > 1 ? 's' : ''} ago`
52
+
53
+ return date.toLocaleDateString()
54
+ }
55
+
56
+ const getTypeStyles = (type?: Activity['type']) => {
57
+ switch (type) {
58
+ case 'success':
59
+ return 'border-green-500 bg-green-50 text-green-900 dark:bg-green-950 dark:text-green-100'
60
+ case 'warning':
61
+ return 'border-yellow-500 bg-yellow-50 text-yellow-900 dark:bg-yellow-950 dark:text-yellow-100'
62
+ case 'error':
63
+ return 'border-red-500 bg-red-50 text-red-900 dark:bg-red-950 dark:text-red-100'
64
+ case 'info':
65
+ return 'border-blue-500 bg-blue-50 text-blue-900 dark:bg-blue-950 dark:text-blue-100'
66
+ default:
67
+ return ''
68
+ }
69
+ }
70
+
71
+ const getUserInitials = (name: string) => {
72
+ return name
73
+ .split(' ')
74
+ .map((n) => n[0])
75
+ .join('')
76
+ .toUpperCase()
77
+ }
78
+
79
+ if (variant === 'timeline') {
80
+ return (
81
+ <div className={cn('space-y-4', className)} {...props}>
82
+ {title && <h3 className="text-lg font-semibold">{title}</h3>}
83
+ <div className="relative">
84
+ <div className="absolute left-6 top-0 h-full w-0.5 bg-border" />
85
+ <div className="space-y-6">
86
+ {activities.map((activity, i) => {
87
+ const Icon = activity.icon
88
+ return (
89
+ <div key={activity.id || i} className="relative flex gap-4">
90
+ <div className="relative z-10 flex h-12 w-12 items-center justify-center">
91
+ <div className="absolute h-3 w-3 rounded-full bg-background border-2 border-primary" />
92
+ </div>
93
+ <div className="flex-1 space-y-1">
94
+ <div className="flex items-start justify-between">
95
+ <div className="space-y-1">
96
+ <p className="text-sm">
97
+ <span className="font-semibold">{activity.user.name}</span>{' '}
98
+ {activity.action}
99
+ {activity.target && (
100
+ <span className="font-medium"> {activity.target}</span>
101
+ )}
102
+ </p>
103
+ {activity.metadata && (
104
+ <div className="flex flex-wrap gap-2">
105
+ {Object.entries(activity.metadata).map(([key, value]) => (
106
+ <Badge key={key} variant="secondary" className="text-xs">
107
+ {key}: {value}
108
+ </Badge>
109
+ ))}
110
+ </div>
111
+ )}
112
+ </div>
113
+ {Icon && <Icon className="h-4 w-4 text-muted-foreground" />}
114
+ </div>
115
+ {showDate && (
116
+ <p className="text-xs text-muted-foreground">
117
+ {formatTime(activity.timestamp)}
118
+ </p>
119
+ )}
120
+ </div>
121
+ </div>
122
+ )
123
+ })}
124
+ </div>
125
+ </div>
126
+ </div>
127
+ )
128
+ }
129
+
130
+ if (variant === 'compact') {
131
+ return (
132
+ <Card className={className} {...props}>
133
+ {title && (
134
+ <CardHeader>
135
+ <CardTitle>{title}</CardTitle>
136
+ </CardHeader>
137
+ )}
138
+ <CardContent className="p-0">
139
+ <div className="divide-y">
140
+ {activities.map((activity, i) => {
141
+ const Icon = activity.icon
142
+ return (
143
+ <div
144
+ key={activity.id || i}
145
+ className={cn(
146
+ 'flex items-center gap-3 px-6 py-3',
147
+ getTypeStyles(activity.type)
148
+ )}
149
+ >
150
+ <Avatar className="h-8 w-8">
151
+ <AvatarImage src={activity.user.avatar} />
152
+ <AvatarFallback className="text-xs">
153
+ {getUserInitials(activity.user.name)}
154
+ </AvatarFallback>
155
+ </Avatar>
156
+ <div className="flex-1 min-w-0">
157
+ <p className="text-sm truncate">
158
+ <span className="font-medium">{activity.user.name}</span>{' '}
159
+ {activity.action}
160
+ {activity.target && (
161
+ <span className="font-medium"> {activity.target}</span>
162
+ )}
163
+ </p>
164
+ </div>
165
+ <div className="flex items-center gap-2">
166
+ {Icon && <Icon className="h-4 w-4 text-muted-foreground" />}
167
+ {showDate && (
168
+ <p className="text-xs text-muted-foreground">
169
+ {formatTime(activity.timestamp)}
170
+ </p>
171
+ )}
172
+ </div>
173
+ </div>
174
+ )
175
+ })}
176
+ </div>
177
+ </CardContent>
178
+ </Card>
179
+ )
180
+ }
181
+
182
+ // Default variant
183
+ return (
184
+ <div className={cn('space-y-4', className)} {...props}>
185
+ {title && <h3 className="text-lg font-semibold">{title}</h3>}
186
+ <div className="space-y-4">
187
+ {activities.map((activity, i) => {
188
+ const Icon = activity.icon
189
+ return (
190
+ <div
191
+ key={activity.id || i}
192
+ className={cn(
193
+ 'flex gap-4 rounded-lg border p-4',
194
+ getTypeStyles(activity.type)
195
+ )}
196
+ >
197
+ <Avatar>
198
+ <AvatarImage src={activity.user.avatar} />
199
+ <AvatarFallback>
200
+ {getUserInitials(activity.user.name)}
201
+ </AvatarFallback>
202
+ </Avatar>
203
+ <div className="flex-1 space-y-1">
204
+ <div className="flex items-start justify-between">
205
+ <div>
206
+ <p className="text-sm">
207
+ <span className="font-semibold">{activity.user.name}</span>{' '}
208
+ {activity.action}
209
+ {activity.target && (
210
+ <span className="font-medium"> {activity.target}</span>
211
+ )}
212
+ </p>
213
+ {activity.user.email && (
214
+ <p className="text-xs text-muted-foreground">
215
+ {activity.user.email}
216
+ </p>
217
+ )}
218
+ </div>
219
+ {Icon && <Icon className="h-4 w-4 text-muted-foreground" />}
220
+ </div>
221
+ {activity.metadata && (
222
+ <div className="flex flex-wrap gap-2 pt-2">
223
+ {Object.entries(activity.metadata).map(([key, value]) => (
224
+ <Badge key={key} variant="secondary" className="text-xs">
225
+ {key}: {value}
226
+ </Badge>
227
+ ))}
228
+ </div>
229
+ )}
230
+ {showDate && (
231
+ <p className="text-xs text-muted-foreground">
232
+ {formatTime(activity.timestamp)}
233
+ </p>
234
+ )}
235
+ </div>
236
+ </div>
237
+ )
238
+ })}
239
+ </div>
240
+ </div>
241
+ )
242
+ }