feat(room-share): add QR code button for easy mobile joining
Add a QR code sharing option alongside existing copy buttons. When clicked, opens a popover with: - QR code encoding the room's share URL - "Scan to Join" heading - Clickable copy button for the URL Uses qrcode.react library with Radix UI popover component. Button styled with orange gradient to differentiate from existing blue link and purple code copy buttons. 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
parent
5927f61c3c
commit
349290ac6a
|
|
@ -66,6 +66,7 @@
|
|||
"next": "^14.2.32",
|
||||
"next-auth": "5.0.0-beta.29",
|
||||
"python-bridge": "^1.1.0",
|
||||
"qrcode.react": "^4.2.0",
|
||||
"react": "^18.2.0",
|
||||
"react-dom": "^18.2.0",
|
||||
"react-resizable-layout": "^0.7.3",
|
||||
|
|
|
|||
|
|
@ -0,0 +1,198 @@
|
|||
import * as Popover from '@radix-ui/react-popover'
|
||||
import type { CSSProperties } from 'react'
|
||||
import { useState } from 'react'
|
||||
import { QRCodeSVG } from 'qrcode.react'
|
||||
import { useClipboard } from '@/hooks/useClipboard'
|
||||
|
||||
export interface QRCodeButtonProps {
|
||||
/**
|
||||
* The URL to encode in the QR code and display
|
||||
*/
|
||||
url: string
|
||||
|
||||
/**
|
||||
* Optional custom styles for the trigger button
|
||||
*/
|
||||
style?: CSSProperties
|
||||
}
|
||||
|
||||
/**
|
||||
* Button that opens a popover with a QR code for the share link
|
||||
* Includes the URL text with a copy button
|
||||
*/
|
||||
export function QRCodeButton({ url, style }: QRCodeButtonProps) {
|
||||
const [open, setOpen] = useState(false)
|
||||
const { copied, copy } = useClipboard()
|
||||
|
||||
const buttonStyles: CSSProperties = {
|
||||
width: '100%',
|
||||
cursor: 'pointer',
|
||||
transition: 'all 0.2s ease',
|
||||
display: 'flex',
|
||||
alignItems: 'center',
|
||||
justifyContent: 'center',
|
||||
gap: '8px',
|
||||
marginBottom: '6px',
|
||||
border: '2px solid rgba(251, 146, 60, 0.4)',
|
||||
background: 'linear-gradient(135deg, rgba(251, 146, 60, 0.2), rgba(251, 146, 60, 0.3))',
|
||||
borderRadius: '8px',
|
||||
padding: '10px 16px',
|
||||
fontSize: '13px',
|
||||
fontWeight: '600',
|
||||
color: 'rgba(253, 186, 116, 1)',
|
||||
...style,
|
||||
}
|
||||
|
||||
const hoverStyles: CSSProperties = {
|
||||
background: 'linear-gradient(135deg, rgba(251, 146, 60, 0.3), rgba(251, 146, 60, 0.4))',
|
||||
borderColor: 'rgba(251, 146, 60, 0.6)',
|
||||
}
|
||||
|
||||
return (
|
||||
<Popover.Root open={open} onOpenChange={setOpen}>
|
||||
<Popover.Trigger asChild>
|
||||
<button
|
||||
type="button"
|
||||
style={buttonStyles}
|
||||
onMouseEnter={(e) => {
|
||||
Object.assign(e.currentTarget.style, hoverStyles)
|
||||
}}
|
||||
onMouseLeave={(e) => {
|
||||
Object.assign(e.currentTarget.style, buttonStyles)
|
||||
}}
|
||||
>
|
||||
<span style={{ fontSize: '16px' }}>📱</span>
|
||||
<span>QR Code</span>
|
||||
</button>
|
||||
</Popover.Trigger>
|
||||
|
||||
<Popover.Portal>
|
||||
<Popover.Content
|
||||
align="center"
|
||||
side="bottom"
|
||||
sideOffset={8}
|
||||
style={{
|
||||
background: 'linear-gradient(135deg, #1a1a2e 0%, #16213e 100%)',
|
||||
border: '2px solid rgba(251, 146, 60, 0.4)',
|
||||
borderRadius: '12px',
|
||||
padding: '20px',
|
||||
boxShadow: '0 8px 32px rgba(0, 0, 0, 0.4)',
|
||||
zIndex: 1000,
|
||||
maxWidth: '320px',
|
||||
}}
|
||||
>
|
||||
{/* Title */}
|
||||
<div
|
||||
style={{
|
||||
fontSize: '16px',
|
||||
fontWeight: 'bold',
|
||||
color: 'rgba(253, 186, 116, 1)',
|
||||
marginBottom: '12px',
|
||||
textAlign: 'center',
|
||||
}}
|
||||
>
|
||||
Scan to Join
|
||||
</div>
|
||||
|
||||
{/* QR Code */}
|
||||
<div
|
||||
style={{
|
||||
background: 'white',
|
||||
padding: '16px',
|
||||
borderRadius: '8px',
|
||||
display: 'flex',
|
||||
justifyContent: 'center',
|
||||
alignItems: 'center',
|
||||
marginBottom: '16px',
|
||||
}}
|
||||
>
|
||||
<QRCodeSVG value={url} size={200} level="H" />
|
||||
</div>
|
||||
|
||||
{/* URL with copy button */}
|
||||
<div
|
||||
style={{
|
||||
marginBottom: '8px',
|
||||
}}
|
||||
>
|
||||
<div
|
||||
style={{
|
||||
fontSize: '11px',
|
||||
color: 'rgba(209, 213, 219, 0.7)',
|
||||
marginBottom: '6px',
|
||||
textAlign: 'center',
|
||||
}}
|
||||
>
|
||||
Or copy link:
|
||||
</div>
|
||||
<button
|
||||
type="button"
|
||||
onClick={() => copy(url)}
|
||||
style={{
|
||||
width: '100%',
|
||||
cursor: 'pointer',
|
||||
transition: 'all 0.2s ease',
|
||||
display: 'flex',
|
||||
alignItems: 'center',
|
||||
justifyContent: 'center',
|
||||
gap: '6px',
|
||||
background: copied
|
||||
? 'linear-gradient(135deg, rgba(34, 197, 94, 0.2), rgba(34, 197, 94, 0.3))'
|
||||
: 'linear-gradient(135deg, rgba(59, 130, 246, 0.2), rgba(59, 130, 246, 0.3))',
|
||||
border: copied
|
||||
? '2px solid rgba(34, 197, 94, 0.5)'
|
||||
: '2px solid rgba(59, 130, 246, 0.4)',
|
||||
borderRadius: '8px',
|
||||
padding: '8px 12px',
|
||||
fontSize: '11px',
|
||||
fontWeight: '600',
|
||||
color: copied ? 'rgba(134, 239, 172, 1)' : 'rgba(147, 197, 253, 1)',
|
||||
}}
|
||||
onMouseEnter={(e) => {
|
||||
if (!copied) {
|
||||
e.currentTarget.style.background =
|
||||
'linear-gradient(135deg, rgba(59, 130, 246, 0.3), rgba(59, 130, 246, 0.4))'
|
||||
e.currentTarget.style.borderColor = 'rgba(59, 130, 246, 0.6)'
|
||||
}
|
||||
}}
|
||||
onMouseLeave={(e) => {
|
||||
if (!copied) {
|
||||
e.currentTarget.style.background =
|
||||
'linear-gradient(135deg, rgba(59, 130, 246, 0.2), rgba(59, 130, 246, 0.3))'
|
||||
e.currentTarget.style.borderColor = 'rgba(59, 130, 246, 0.4)'
|
||||
}
|
||||
}}
|
||||
>
|
||||
{copied ? (
|
||||
<>
|
||||
<span style={{ fontSize: '12px' }}>✓</span>
|
||||
<span>Copied!</span>
|
||||
</>
|
||||
) : (
|
||||
<>
|
||||
<span style={{ fontSize: '12px' }}>🔗</span>
|
||||
<span
|
||||
style={{
|
||||
overflow: 'hidden',
|
||||
textOverflow: 'ellipsis',
|
||||
whiteSpace: 'nowrap',
|
||||
maxWidth: '200px',
|
||||
}}
|
||||
>
|
||||
{url}
|
||||
</span>
|
||||
</>
|
||||
)}
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<Popover.Arrow
|
||||
style={{
|
||||
fill: 'rgba(251, 146, 60, 0.4)',
|
||||
}}
|
||||
/>
|
||||
</Popover.Content>
|
||||
</Popover.Portal>
|
||||
</Popover.Root>
|
||||
)
|
||||
}
|
||||
|
|
@ -1,4 +1,5 @@
|
|||
import { CopyButton } from '@/components/common/CopyButton'
|
||||
import { QRCodeButton } from '@/components/common/QRCodeButton'
|
||||
|
||||
export interface RoomShareButtonsProps {
|
||||
/**
|
||||
|
|
@ -41,6 +42,8 @@ export function RoomShareButtons({ joinCode, shareUrl }: RoomShareButtonsProps)
|
|||
}
|
||||
copiedLabel="Link Copied!"
|
||||
/>
|
||||
|
||||
<QRCodeButton url={shareUrl} />
|
||||
</>
|
||||
)
|
||||
}
|
||||
|
|
|
|||
Loading…
Reference in New Issue