Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
38 changes: 16 additions & 22 deletions src/features/dashboard/settings/general/team-info.tsx
Original file line number Diff line number Diff line change
@@ -1,30 +1,24 @@
'use client'

import { ClipboardEvent } from 'react'
import { useDashboard } from '@/features/dashboard/context'
import { formatDate } from '@/lib/utils/formatting'
import CopyButtonInline from '@/ui/copy-button-inline'

const InfoRow = ({ label, value }: { label: string; value: string }) => {
const handleCopy = (e: ClipboardEvent<HTMLSpanElement>) => {
if (!window.getSelection()?.toString()) return
e.preventDefault()
e.clipboardData.setData('text/plain', value)
}

return (
<div className="flex flex-col gap-1 sm:flex-row sm:items-center sm:justify-between sm:gap-4">
<span className="text-fg-tertiary shrink-0 text-xs leading-[17px] font-normal uppercase">
{label}
</span>
<span
className="text-fg-secondary font-mono text-base leading-5 font-semibold tracking-[-0.16px] uppercase [overflow-wrap:anywhere]"
onCopy={handleCopy}
>
{value}
</span>
</div>
)
}
const InfoRow = ({ label, value }: { label: string; value: string }) => (
<div className="flex flex-col gap-1 sm:flex-row sm:items-center sm:justify-between sm:gap-4">
<span className="text-fg-tertiary shrink-0 text-xs leading-[17px] font-normal uppercase">
{label}
</span>
<CopyButtonInline
value={value}
iconPosition="left"
aria-label={`Copy ${label}`}
className="text-fg-secondary text-sm leading-5 font-normal"
>
{value}
</CopyButtonInline>
</div>
)

export const TeamInfo = () => {
const { team } = useDashboard()
Expand Down
68 changes: 50 additions & 18 deletions src/ui/copy-button-inline.tsx
Original file line number Diff line number Diff line change
@@ -1,47 +1,79 @@
'use client'

import { AnimatePresence, motion } from 'motion/react'
import { useClipboard } from '@/lib/hooks/use-clipboard'
import { cn } from '@/lib/utils/ui'
import { cn, EASE_APPEAR } from '@/lib/utils/ui'
import { CheckIcon, CopyIcon } from '@/ui/primitives/icons'

export default function CopyButtonInline({
value,
children,
className,
iconPosition = 'right',
'aria-label': ariaLabel,
}: {
value: string
children: React.ReactNode
className?: string
iconPosition?: 'left' | 'right'
'aria-label'?: string
}) {
const [wasCopied, copy] = useClipboard(2000)
const [wasCopied, copy] = useClipboard(1000)

const handleClick = (e: React.MouseEvent) => {
e.stopPropagation()
copy(value)
}

const icon = (
<span
aria-hidden="true"
className={cn(
'inline-flex shrink-0 items-center justify-center transition-opacity [&_svg]:size-3.5',
wasCopied
? 'opacity-100 [&_svg]:text-icon'
: 'opacity-0 [&_svg]:text-icon-secondary group-hover/copy:opacity-100 group-focus-visible/copy:opacity-100'
)}
>
<AnimatePresence mode="wait" initial={false}>
{wasCopied ? (
<motion.span
key="check"
initial={{ opacity: 0.2, scale: 0.97, filter: 'blur(1px)' }}
animate={{ opacity: 1, scale: 1.2, filter: 'blur(0px)' }}
exit={{ opacity: 0.2, scale: 0.97, filter: 'blur(1px)' }}
transition={{ duration: 0.1, ease: EASE_APPEAR }}
>
<CheckIcon />
</motion.span>
) : (
<motion.span
key="copy"
initial={{ opacity: 0.2, scale: 0.9, filter: 'blur(1px)' }}
animate={{ opacity: 1, scale: 1, filter: 'blur(0px)' }}
exit={{ opacity: 0.2, scale: 0.9, filter: 'blur(1px)' }}
transition={{ duration: 0.1, ease: EASE_APPEAR }}
>
<CopyIcon />
</motion.span>
)}
</AnimatePresence>
</span>
)

return (
<button
type="button"
onClick={handleClick}
aria-label={ariaLabel}
className={cn(
'relative inline-flex min-w-0 items-center bg-transparent p-0 text-left hover:opacity-80',
'group/copy cursor-pointer border-0',
'group/copy inline-flex min-w-0 cursor-pointer items-center gap-1.5 border-0 bg-transparent p-0 text-left hover:opacity-80',
className
)}
>
<span className="truncate pr-4">{children}</span>
<span
className={cn(
'absolute right-0 top-1/2 -translate-y-1/2 opacity-0 group-hover/copy:opacity-100',
wasCopied && 'opacity-100!'
)}
aria-hidden="true"
>
{wasCopied ? (
<CheckIcon className="size-3.5 text-icon" />
) : (
<CopyIcon className="size-3.5 text-icon-secondary" />
)}
</span>
{iconPosition === 'left' && icon}
<span className="truncate">{children}</span>
{iconPosition === 'right' && icon}
</button>
)
}
Loading