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
|
<AudioBubble
|
||||||
key={event.id}
|
key={event.id}
|
||||||
eventId={event.id}
|
eventId={event.id}
|
||||||
|
event={event}
|
||||||
url={url}
|
url={url}
|
||||||
isPlaying={event.id === playingEventId}
|
isPlaying={event.id === playingEventId}
|
||||||
onClick={handleBubbleClick}
|
onClick={handleBubbleClick}
|
||||||
|
@@ -1,16 +1,23 @@
|
|||||||
"use client"
|
"use client"
|
||||||
|
|
||||||
import { useEffect, useRef, useState } from "react"
|
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 {
|
interface AudioBubbleProps {
|
||||||
eventId: string
|
eventId: string
|
||||||
|
event: NostrEvent
|
||||||
url: string
|
url: string
|
||||||
isPlaying: boolean
|
isPlaying: boolean
|
||||||
onClick: (url: string, eventId: string) => void
|
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 bubbleRef = useRef<HTMLDivElement>(null)
|
||||||
const [position, setPosition] = useState({
|
const [position, setPosition] = useState({
|
||||||
x: Math.random() * 80 + 10, // 10-90% of screen width
|
x: Math.random() * 80 + 10, // 10-90% of screen width
|
||||||
@@ -67,11 +74,10 @@ export default function AudioBubble({ eventId, url, isPlaying, onClick }: AudioB
|
|||||||
return (
|
return (
|
||||||
<div
|
<div
|
||||||
ref={bubbleRef}
|
ref={bubbleRef}
|
||||||
className={`absolute rounded-full flex items-center justify-center cursor-pointer transition-all duration-300 ${
|
className={`absolute rounded-full flex items-center justify-center cursor-pointer transition-all duration-300 ${isPlaying
|
||||||
isPlaying
|
|
||||||
? "bg-white/90 shadow-lg shadow-white/30"
|
? "bg-white/90 shadow-lg shadow-white/30"
|
||||||
: "bg-white/70 hover:bg-white/80"
|
: "bg-white/70 hover:bg-white/80"
|
||||||
}`}
|
}`}
|
||||||
style={{
|
style={{
|
||||||
left: `${position.x}%`,
|
left: `${position.x}%`,
|
||||||
top: `${position.y}%`,
|
top: `${position.y}%`,
|
||||||
@@ -86,7 +92,12 @@ export default function AudioBubble({ eventId, url, isPlaying, onClick }: AudioB
|
|||||||
}}
|
}}
|
||||||
onClick={handleClick}
|
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 && (
|
{isPlaying && (
|
||||||
<div className="absolute -bottom-5 left-1/2 transform -translate-x-1/2 text-white/50 text-[10px] whitespace-nowrap animate-pulse">
|
<div className="absolute -bottom-5 left-1/2 transform -translate-x-1/2 text-white/50 text-[10px] whitespace-nowrap animate-pulse">
|
||||||
|
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",
|
"name": "nostr-voice",
|
||||||
"version": "0.1.0",
|
"version": "0.1.0",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
|
"@radix-ui/react-avatar": "^1.1.10",
|
||||||
"@radix-ui/react-dialog": "^1.1.14",
|
"@radix-ui/react-dialog": "^1.1.14",
|
||||||
"@radix-ui/react-slot": "^1.2.3",
|
"@radix-ui/react-slot": "^1.2.3",
|
||||||
"class-variance-authority": "^0.7.1",
|
"class-variance-authority": "^0.7.1",
|
||||||
@@ -1008,6 +1009,33 @@
|
|||||||
"integrity": "sha512-XnbHrrprsNqZKQhStrSwgRUQzoCI1glLzdw79xiZPoofhGICeZRSQ3dIxAKH1gb3OHfNf4d6f+vAv3kil2eggA==",
|
"integrity": "sha512-XnbHrrprsNqZKQhStrSwgRUQzoCI1glLzdw79xiZPoofhGICeZRSQ3dIxAKH1gb3OHfNf4d6f+vAv3kil2eggA==",
|
||||||
"license": "MIT"
|
"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": {
|
"node_modules/@radix-ui/react-compose-refs": {
|
||||||
"version": "1.1.2",
|
"version": "1.1.2",
|
||||||
"resolved": "https://registry.npmjs.org/@radix-ui/react-compose-refs/-/react-compose-refs-1.1.2.tgz",
|
"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": {
|
"node_modules/@radix-ui/react-use-layout-effect": {
|
||||||
"version": "1.1.1",
|
"version": "1.1.1",
|
||||||
"resolved": "https://registry.npmjs.org/@radix-ui/react-use-layout-effect/-/react-use-layout-effect-1.1.1.tgz",
|
"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": {
|
"node_modules/vaul": {
|
||||||
"version": "1.1.2",
|
"version": "1.1.2",
|
||||||
"resolved": "https://registry.npmjs.org/vaul/-/vaul-1.1.2.tgz",
|
"resolved": "https://registry.npmjs.org/vaul/-/vaul-1.1.2.tgz",
|
||||||
|
@@ -9,6 +9,7 @@
|
|||||||
"lint": "next lint"
|
"lint": "next lint"
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
|
"@radix-ui/react-avatar": "^1.1.10",
|
||||||
"@radix-ui/react-dialog": "^1.1.14",
|
"@radix-ui/react-dialog": "^1.1.14",
|
||||||
"@radix-ui/react-slot": "^1.2.3",
|
"@radix-ui/react-slot": "^1.2.3",
|
||||||
"class-variance-authority": "^0.7.1",
|
"class-variance-authority": "^0.7.1",
|
||||||
|
Reference in New Issue
Block a user