@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.
- package/assets/general.tsx +1 -1
- package/assets/hanzo-logo.tsx +3 -1
- package/assets/index.ts +119 -5
- package/blocks/auth/index.ts +6 -0
- package/blocks/auth/login-2fa.tsx +165 -0
- package/blocks/auth/login-basic.tsx +94 -0
- package/blocks/auth/login-social.tsx +148 -0
- package/blocks/auth/magic-link.tsx +129 -0
- package/blocks/auth/password-reset.tsx +97 -0
- package/blocks/auth/signup.tsx +157 -0
- package/blocks/data-display/activity-feed.tsx +242 -0
- package/blocks/data-display/data-table.tsx +235 -0
- package/blocks/data-display/stats-grid.tsx +194 -0
- package/blocks/ecommerce/checkout.tsx +242 -0
- package/blocks/ecommerce/index.ts +7 -0
- package/blocks/ecommerce/product-detail.tsx +257 -0
- package/blocks/ecommerce/product-grid.tsx +148 -0
- package/blocks/ecommerce/shopping-cart.tsx +181 -0
- package/blocks/marketing/cta-section.tsx +207 -0
- package/blocks/marketing/faq.tsx +159 -0
- package/blocks/marketing/features-grid.tsx +156 -0
- package/blocks/marketing/hero-section.tsx +192 -0
- package/blocks/marketing/index.ts +6 -0
- package/blocks/marketing/pricing-table.tsx +121 -0
- package/blocks/marketing/testimonials.tsx +196 -0
- package/components/index.ts +4 -51
- package/dist/index.js +9351 -0
- package/dist/index.mjs +9340 -0
- package/dist/lib/utils.js +47 -0
- package/dist/lib/utils.mjs +28 -0
- package/dist/src/utils.js +47 -0
- package/dist/src/utils.mjs +28 -0
- package/dist/tailwind/index.js +2050 -0
- package/dist/tailwind/index.mjs +2019 -0
- package/dist/types/index.js +79 -0
- package/dist/types/index.mjs +56 -0
- package/dist/util/format-text.js +51 -0
- package/dist/util/format-text.mjs +32 -0
- package/dist/util/index.js +384 -0
- package/dist/util/index.mjs +363 -0
- package/frameworks/core/index.ts +6 -0
- package/frameworks/core/utils/index.ts +64 -0
- package/frameworks/react/components/button.tsx +26 -0
- package/frameworks/react/components/index.ts +5 -0
- package/frameworks/react/hooks/index.ts +5 -0
- package/frameworks/react/index.ts +9 -0
- package/frameworks/react/package.json +8 -0
- package/frameworks/react/utils/index.ts +2 -0
- package/frameworks/react-native/index.ts +9 -0
- package/frameworks/react-native/package.json +8 -0
- package/frameworks/registry.json +371 -0
- package/frameworks/setup.sh +69 -0
- package/frameworks/svelte/index.ts +9 -0
- package/frameworks/svelte/package.json +8 -0
- package/frameworks/tracker.json +1854 -0
- package/frameworks/vue/index.ts +9 -0
- package/frameworks/vue/package.json +8 -0
- package/package.json +192 -28
- package/primitives/accordion.tsx +1 -1
- package/primitives/alert-dialog.tsx +1 -1
- package/primitives/alert.tsx +1 -1
- package/primitives/avatar.tsx +1 -1
- package/primitives/badge.tsx +2 -1
- package/primitives/breadcrumb.tsx +1 -1
- package/primitives/button.tsx +37 -47
- package/primitives/card.tsx +1 -1
- package/primitives/carousel.tsx +3 -2
- package/primitives/chat/chat-input-area.tsx +5 -4
- package/primitives/chat/chat-input.tsx +2 -2
- package/primitives/chat/files-preview.tsx +5 -4
- package/primitives/chat/message-list.tsx +2 -1
- package/primitives/chat/sqlite-preview.tsx +8 -8
- package/primitives/checkbox.tsx +2 -1
- package/primitives/command.tsx +3 -1
- package/primitives/context-menu.tsx +1 -1
- package/primitives/dialog.tsx +6 -1
- package/primitives/drawer.tsx +4 -1
- package/primitives/dropdown-menu.tsx +1 -1
- package/primitives/file-uploader.tsx +4 -2
- package/primitives/form.tsx +1 -1
- package/primitives/hover-card.tsx +1 -1
- package/primitives/icons/github.tsx +2 -2
- package/primitives/icons/youtube-logo.tsx +1 -1
- package/primitives/index-common.ts +7 -6
- package/primitives/input-otp.tsx +1 -1
- package/primitives/input.tsx +2 -1
- package/primitives/label.tsx +2 -1
- package/primitives/markdown-preview.tsx +3 -0
- package/primitives/mermaid.tsx +13 -18
- package/primitives/next/image.tsx +2 -1
- package/primitives/next/inline-icon.tsx +14 -14
- package/primitives/next/media-stack.tsx +2 -19
- package/primitives/pagination.tsx +1 -1
- package/primitives/popover.tsx +4 -2
- package/primitives/progress.tsx +2 -1
- package/primitives/prompt-textarea.tsx +1 -1
- package/primitives/radio-group.tsx +1 -1
- package/primitives/scroll-area.tsx +1 -1
- package/primitives/search-input.tsx +1 -1
- package/primitives/select.tsx +1 -1
- package/primitives/separator.tsx +2 -1
- package/primitives/sheet.tsx +1 -1
- package/primitives/skeleton.tsx +1 -0
- package/primitives/slider.tsx +2 -1
- package/primitives/stepper.tsx +1 -1
- package/primitives/switch.tsx +2 -1
- package/primitives/table.tsx +1 -1
- package/primitives/tabs.tsx +1 -1
- package/primitives/textarea.tsx +2 -1
- package/primitives/textfield.tsx +1 -0
- package/primitives/toggle-group.tsx +1 -1
- package/primitives/toggle.tsx +1 -1
- package/primitives/tooltip.tsx +1 -1
- package/src/hooks/use-copy-clipboard.ts +1 -1
- package/src/index-lean.ts +87 -0
- package/src/index.ts +54 -0
- package/src/registry/api.ts +1 -1
- package/src/utils.ts +19 -1
- package/tailwind/tailwind.config.hanzo-preset.js +7 -7
- package/tailwind/typo-plugin/index.js +1 -1
- package/types/animation-def.ts +1 -1
- package/types/index.ts +2 -1
- package/util/blob.ts +9 -4
- package/util/date.ts +2 -1
- package/util/format-text.ts +2 -1
- package/util/index.ts +103 -0
- package/util/spread-to-transform.ts +9 -8
- package/MCP-INSTRUCTIONS.md +0 -73
- package/README-MCP.md +0 -175
- package/dist/button.d.ts +0 -1
- package/dist/button.js +0 -1
- package/dist/hooks/index.d.ts +0 -7
- package/dist/hooks/index.js +0 -7
- package/dist/hooks/use-click-away.d.ts +0 -2
- package/dist/hooks/use-click-away.js +0 -23
- package/dist/hooks/use-combined-refs.d.ts +0 -3
- package/dist/hooks/use-combined-refs.js +0 -18
- package/dist/hooks/use-copy-clipboard.d.ts +0 -9
- package/dist/hooks/use-copy-clipboard.js +0 -21
- package/dist/hooks/use-debounce.d.ts +0 -1
- package/dist/hooks/use-debounce.js +0 -13
- package/dist/hooks/use-fill-ids.d.ts +0 -8
- package/dist/hooks/use-fill-ids.js +0 -20
- package/dist/hooks/use-map.d.ts +0 -1
- package/dist/hooks/use-map.js +0 -20
- package/dist/hooks/use-measure.d.ts +0 -8
- package/dist/hooks/use-measure.js +0 -25
- package/dist/hooks/use-reverse-video-playback.d.ts +0 -1
- package/dist/hooks/use-reverse-video-playback.js +0 -41
- package/dist/hooks/use-scroll-restoration.d.ts +0 -8
- package/dist/hooks/use-scroll-restoration.js +0 -36
- package/dist/mcp/enhanced-server.d.ts +0 -29
- package/dist/mcp/enhanced-server.js +0 -1128
- package/dist/mcp/index.d.ts +0 -28
- package/dist/mcp/index.js +0 -436
- package/dist/registry/api.d.ts +0 -37
- package/dist/registry/api.js +0 -129
- package/dist/registry/index.d.ts +0 -353
- package/dist/registry/index.js +0 -45
- package/dist/utils.d.ts +0 -1
- package/dist/utils.js +0 -1
- package/environment.d.ts +0 -6
- package/public/r/accordion.json +0 -11
- package/public/r/alert.json +0 -11
- package/public/r/avatar.json +0 -11
- package/public/r/badge.json +0 -11
- package/public/r/button.json +0 -11
- package/public/r/card.json +0 -11
- package/public/r/checkbox.json +0 -11
- package/public/r/default.json +0 -6
- package/public/r/dialog.json +0 -11
- package/public/r/input.json +0 -11
- package/public/r/label.json +0 -11
- package/public/r/new-york.json +0 -6
- package/public/r/popover.json +0 -11
- package/public/r/select.json +0 -11
- package/public/r/table.json +0 -11
- package/public/r/tabs.json +0 -11
- package/public/r/toast.json +0 -11
- package/registry.json +0 -184
- package/test/test-registry.js +0 -73
- package/test-imports.mjs +0 -19
- package/tsconfig.json +0 -22
- 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'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
|
+
}
|