Add Avatar component and integrate it into AudioBubble; update package dependencies
This commit is contained in:
@@ -98,6 +98,7 @@ export default function Home() {
|
||||
<AudioBubble
|
||||
key={event.id}
|
||||
eventId={event.id}
|
||||
event={event}
|
||||
url={url}
|
||||
isPlaying={event.id === playingEventId}
|
||||
onClick={handleBubbleClick}
|
||||
|
@@ -1,16 +1,23 @@
|
||||
"use client"
|
||||
|
||||
import { useEffect, useRef, useState } from "react"
|
||||
import { Music } from "lucide-react"
|
||||
// import { Music } from "lucide-react"
|
||||
import { useProfile } from "nostr-react"
|
||||
import { Event as NostrEvent } from "nostr-tools"
|
||||
import { Avatar, AvatarFallback, AvatarImage } from "@/components/ui/avatar"
|
||||
|
||||
interface AudioBubbleProps {
|
||||
eventId: string
|
||||
event: NostrEvent
|
||||
url: string
|
||||
isPlaying: boolean
|
||||
onClick: (url: string, eventId: string) => void
|
||||
}
|
||||
|
||||
export default function AudioBubble({ eventId, url, isPlaying, onClick }: AudioBubbleProps) {
|
||||
export default function AudioBubble({ eventId, event, url, isPlaying, onClick }: AudioBubbleProps) {
|
||||
|
||||
const { data } = useProfile({ pubkey: event.pubkey })
|
||||
|
||||
const bubbleRef = useRef<HTMLDivElement>(null)
|
||||
const [position, setPosition] = useState({
|
||||
x: Math.random() * 80 + 10, // 10-90% of screen width
|
||||
@@ -67,11 +74,10 @@ export default function AudioBubble({ eventId, url, isPlaying, onClick }: AudioB
|
||||
return (
|
||||
<div
|
||||
ref={bubbleRef}
|
||||
className={`absolute rounded-full flex items-center justify-center cursor-pointer transition-all duration-300 ${
|
||||
isPlaying
|
||||
? "bg-white/90 shadow-lg shadow-white/30"
|
||||
className={`absolute rounded-full flex items-center justify-center cursor-pointer transition-all duration-300 ${isPlaying
|
||||
? "bg-white/90 shadow-lg shadow-white/30"
|
||||
: "bg-white/70 hover:bg-white/80"
|
||||
}`}
|
||||
}`}
|
||||
style={{
|
||||
left: `${position.x}%`,
|
||||
top: `${position.y}%`,
|
||||
@@ -80,14 +86,19 @@ export default function AudioBubble({ eventId, url, isPlaying, onClick }: AudioB
|
||||
transform: "translate(-50%, -50%)",
|
||||
zIndex: isPlaying ? 10 : 1,
|
||||
backdropFilter: "blur(2px)",
|
||||
boxShadow: isPlaying
|
||||
? "0 0 15px 5px rgba(255, 255, 255, 0.3)"
|
||||
boxShadow: isPlaying
|
||||
? "0 0 15px 5px rgba(255, 255, 255, 0.3)"
|
||||
: "0 0 10px 2px rgba(255, 255, 255, 0.15)"
|
||||
}}
|
||||
onClick={handleClick}
|
||||
>
|
||||
<Music className={`${isPlaying ? "animate-pulse text-black/90" : "text-black/70"}`} size={size * 0.35} />
|
||||
|
||||
{/* <Music className={`${isPlaying ? "animate-pulse text-black/90" : "text-black/70"}`} size={size * 0.35} /> */}
|
||||
<Avatar>
|
||||
<AvatarImage src={data?.picture} />
|
||||
<AvatarFallback>{data?.name}</AvatarFallback>
|
||||
</Avatar>
|
||||
|
||||
|
||||
{isPlaying && (
|
||||
<div className="absolute -bottom-5 left-1/2 transform -translate-x-1/2 text-white/50 text-[10px] whitespace-nowrap animate-pulse">
|
||||
Info ↑
|
||||
|
53
components/ui/avatar.tsx
Normal file
53
components/ui/avatar.tsx
Normal file
@@ -0,0 +1,53 @@
|
||||
"use client"
|
||||
|
||||
import * as React from "react"
|
||||
import * as AvatarPrimitive from "@radix-ui/react-avatar"
|
||||
|
||||
import { cn } from "@/lib/utils"
|
||||
|
||||
function Avatar({
|
||||
className,
|
||||
...props
|
||||
}: React.ComponentProps<typeof AvatarPrimitive.Root>) {
|
||||
return (
|
||||
<AvatarPrimitive.Root
|
||||
data-slot="avatar"
|
||||
className={cn(
|
||||
"relative flex size-8 shrink-0 overflow-hidden rounded-full",
|
||||
className
|
||||
)}
|
||||
{...props}
|
||||
/>
|
||||
)
|
||||
}
|
||||
|
||||
function AvatarImage({
|
||||
className,
|
||||
...props
|
||||
}: React.ComponentProps<typeof AvatarPrimitive.Image>) {
|
||||
return (
|
||||
<AvatarPrimitive.Image
|
||||
data-slot="avatar-image"
|
||||
className={cn("aspect-square size-full", className)}
|
||||
{...props}
|
||||
/>
|
||||
)
|
||||
}
|
||||
|
||||
function AvatarFallback({
|
||||
className,
|
||||
...props
|
||||
}: React.ComponentProps<typeof AvatarPrimitive.Fallback>) {
|
||||
return (
|
||||
<AvatarPrimitive.Fallback
|
||||
data-slot="avatar-fallback"
|
||||
className={cn(
|
||||
"bg-muted flex size-full items-center justify-center rounded-full",
|
||||
className
|
||||
)}
|
||||
{...props}
|
||||
/>
|
||||
)
|
||||
}
|
||||
|
||||
export { Avatar, AvatarImage, AvatarFallback }
|
55
package-lock.json
generated
55
package-lock.json
generated
@@ -8,6 +8,7 @@
|
||||
"name": "nostr-voice",
|
||||
"version": "0.1.0",
|
||||
"dependencies": {
|
||||
"@radix-ui/react-avatar": "^1.1.10",
|
||||
"@radix-ui/react-dialog": "^1.1.14",
|
||||
"@radix-ui/react-slot": "^1.2.3",
|
||||
"class-variance-authority": "^0.7.1",
|
||||
@@ -1008,6 +1009,33 @@
|
||||
"integrity": "sha512-XnbHrrprsNqZKQhStrSwgRUQzoCI1glLzdw79xiZPoofhGICeZRSQ3dIxAKH1gb3OHfNf4d6f+vAv3kil2eggA==",
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/@radix-ui/react-avatar": {
|
||||
"version": "1.1.10",
|
||||
"resolved": "https://registry.npmjs.org/@radix-ui/react-avatar/-/react-avatar-1.1.10.tgz",
|
||||
"integrity": "sha512-V8piFfWapM5OmNCXTzVQY+E1rDa53zY+MQ4Y7356v4fFz6vqCyUtIz2rUD44ZEdwg78/jKmMJHj07+C/Z/rcog==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@radix-ui/react-context": "1.1.2",
|
||||
"@radix-ui/react-primitive": "2.1.3",
|
||||
"@radix-ui/react-use-callback-ref": "1.1.1",
|
||||
"@radix-ui/react-use-is-hydrated": "0.1.0",
|
||||
"@radix-ui/react-use-layout-effect": "1.1.1"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"@types/react": "*",
|
||||
"@types/react-dom": "*",
|
||||
"react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc",
|
||||
"react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc"
|
||||
},
|
||||
"peerDependenciesMeta": {
|
||||
"@types/react": {
|
||||
"optional": true
|
||||
},
|
||||
"@types/react-dom": {
|
||||
"optional": true
|
||||
}
|
||||
}
|
||||
},
|
||||
"node_modules/@radix-ui/react-compose-refs": {
|
||||
"version": "1.1.2",
|
||||
"resolved": "https://registry.npmjs.org/@radix-ui/react-compose-refs/-/react-compose-refs-1.1.2.tgz",
|
||||
@@ -1318,6 +1346,24 @@
|
||||
}
|
||||
}
|
||||
},
|
||||
"node_modules/@radix-ui/react-use-is-hydrated": {
|
||||
"version": "0.1.0",
|
||||
"resolved": "https://registry.npmjs.org/@radix-ui/react-use-is-hydrated/-/react-use-is-hydrated-0.1.0.tgz",
|
||||
"integrity": "sha512-U+UORVEq+cTnRIaostJv9AGdV3G6Y+zbVd+12e18jQ5A3c0xL03IhnHuiU4UV69wolOQp5GfR58NW/EgdQhwOA==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"use-sync-external-store": "^1.5.0"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"@types/react": "*",
|
||||
"react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc"
|
||||
},
|
||||
"peerDependenciesMeta": {
|
||||
"@types/react": {
|
||||
"optional": true
|
||||
}
|
||||
}
|
||||
},
|
||||
"node_modules/@radix-ui/react-use-layout-effect": {
|
||||
"version": "1.1.1",
|
||||
"resolved": "https://registry.npmjs.org/@radix-ui/react-use-layout-effect/-/react-use-layout-effect-1.1.1.tgz",
|
||||
@@ -6678,6 +6724,15 @@
|
||||
}
|
||||
}
|
||||
},
|
||||
"node_modules/use-sync-external-store": {
|
||||
"version": "1.5.0",
|
||||
"resolved": "https://registry.npmjs.org/use-sync-external-store/-/use-sync-external-store-1.5.0.tgz",
|
||||
"integrity": "sha512-Rb46I4cGGVBmjamjphe8L/UnvJD+uPPtTkNvX5mZgqdbavhI4EbgIWJiIHXJ8bc/i9EQGPRh4DwEURJ552Do0A==",
|
||||
"license": "MIT",
|
||||
"peerDependencies": {
|
||||
"react": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/vaul": {
|
||||
"version": "1.1.2",
|
||||
"resolved": "https://registry.npmjs.org/vaul/-/vaul-1.1.2.tgz",
|
||||
|
@@ -9,6 +9,7 @@
|
||||
"lint": "next lint"
|
||||
},
|
||||
"dependencies": {
|
||||
"@radix-ui/react-avatar": "^1.1.10",
|
||||
"@radix-ui/react-dialog": "^1.1.14",
|
||||
"@radix-ui/react-slot": "^1.2.3",
|
||||
"class-variance-authority": "^0.7.1",
|
||||
|
Reference in New Issue
Block a user