Skip to content

Commit

Permalink
FEAT: Copy badge (#43)
Browse files Browse the repository at this point in the history
* FEAT: Copy badge

* CHORE: use english instead

* FIX: Swap text

* CHORE: use green color

* FIX: Set to disable on copied
  • Loading branch information
mazipan authored Nov 22, 2024
1 parent c65df88 commit ca1e689
Show file tree
Hide file tree
Showing 4 changed files with 200 additions and 1 deletion.
52 changes: 52 additions & 0 deletions src/components/CopyButton.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
'use client'

import { copyTextToClipboard } from '@/lib/utils'
import { Check, Copy } from 'lucide-react'
import { useState } from 'react'

import { Button } from './ui/button'
import { Input } from './ui/input'

export function CopyButton({
text,
withLabel = false,
withInput = false,
fullWidth = false,
}: {
text: string
withLabel?: boolean
withInput?: boolean
fullWidth?: boolean
}) {
const [isCopied, setIsCopied] = useState(false)

return (
<div className={`flex gap-2 ${fullWidth ? 'w-full' : ''}`}>
{withInput ? <Input value={text} readOnly /> : null}

<Button
variant="outline"
className="flex gap-2 items-center"
type="button"
disabled={isCopied}
onClick={() => {
setIsCopied(true);
copyTextToClipboard(`${text}`);

setTimeout(() => {
setIsCopied(false);
}, 3000);
}}
>
{isCopied ? (
<Check className="h-4 w-4 shrink-0 text-green-500" />
) : (
<Copy className="h-4 w-4 shrink-0" />
)}
{withLabel ? (
<>{isCopied ? <span>Copied</span> : <span>Copy</span>}</>
) : null}
</Button>
</div>
);
}
59 changes: 59 additions & 0 deletions src/components/ui/alert.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,59 @@
import * as React from "react"
import { cva, type VariantProps } from "class-variance-authority"

import { cn } from "@/lib/utils"

const alertVariants = cva(
"relative w-full rounded-lg border p-4 [&>svg~*]:pl-7 [&>svg+div]:translate-y-[-3px] [&>svg]:absolute [&>svg]:left-4 [&>svg]:top-4 [&>svg]:text-foreground",
{
variants: {
variant: {
default: "bg-background text-foreground",
destructive:
"border-destructive/50 text-destructive dark:border-destructive [&>svg]:text-destructive",
},
},
defaultVariants: {
variant: "default",
},
}
)

const Alert = React.forwardRef<
HTMLDivElement,
React.HTMLAttributes<HTMLDivElement> & VariantProps<typeof alertVariants>
>(({ className, variant, ...props }, ref) => (
<div
ref={ref}
role="alert"
className={cn(alertVariants({ variant }), className)}
{...props}
/>
))
Alert.displayName = "Alert"

const AlertTitle = React.forwardRef<
HTMLParagraphElement,
React.HTMLAttributes<HTMLHeadingElement>
>(({ className, ...props }, ref) => (
<h5
ref={ref}
className={cn("mb-1 font-medium leading-none tracking-tight", className)}
{...props}
/>
))
AlertTitle.displayName = "AlertTitle"

const AlertDescription = React.forwardRef<
HTMLParagraphElement,
React.HTMLAttributes<HTMLParagraphElement>
>(({ className, ...props }, ref) => (
<div
ref={ref}
className={cn("text-sm [&_p]:leading-relaxed", className)}
{...props}
/>
))
AlertDescription.displayName = "AlertDescription"

export { Alert, AlertTitle, AlertDescription }
10 changes: 9 additions & 1 deletion src/lib/utils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -84,4 +84,12 @@ export const shareToSocial = (user: User) => {
)}`;

window.open(url, '_blank');
};
};

export async function copyTextToClipboard(text: string) {
if ('clipboard' in navigator) {
return await navigator.clipboard.writeText(text);
}

return document.execCommand('copy', true, text);
}
80 changes: 80 additions & 0 deletions src/views/Detail.tsx
Original file line number Diff line number Diff line change
@@ -1,25 +1,38 @@
import { CopyButton } from '@/components/CopyButton';
import { EmptyState } from '@/components/EmptyState';
import { GhCalendar } from '@/components/GhCalendar';
import { Spinner } from '@/components/Spinner';
import { DEFAULT_CLASSNAMES_RANK, renderRank } from '@/components/TableUser/column';
import { Alert, AlertDescription, AlertTitle } from '@/components/ui/alert';
import { Avatar, AvatarFallback, AvatarImage } from '@/components/ui/avatar';
import { Button } from '@/components/ui/button';
import {
Select,
SelectContent,
SelectItem,
SelectTrigger,
SelectValue,
} from '@/components/ui/select';
import { useMostActiveUsers } from '@/lib/api';
import { formatNumber, makeInitial, shareToSocial } from '@/lib/utils';
import {
ActivityIcon,
ArrowLeftIcon,
ExternalLink,
GithubIcon,
InfoIcon,
Share2Icon,
UserCheck2Icon,
} from 'lucide-react';
import { useState } from 'react';
import { Link, useParams } from 'react-router-dom';

export const Detail = () => {
const { data, isLoading, isError } = useMostActiveUsers();
const { username } = useParams();

const [badgeType, setBadgeType] = useState<string>('markdown');

if (isLoading)
return (
<div className="relative py-16 max-w-4xl mx-auto flex flex-col justify-center items-center gap-8 text-center">
Expand Down Expand Up @@ -149,6 +162,73 @@ export const Detail = () => {
/>
</div>
</div>

<div className="relative py-16 max-w-4xl mx-auto flex flex-col justify-center items-center gap-8">
<h2 className="text-center scroll-m-20 pb-2 text-3xl font-semibold tracking-tight first:mt-0">
Create your badge
</h2>

<div className="relative flex flex-col gap-4">
<img
src={`https://indogithubers-badge.vercel.app/badge?username=${username}`}
alt="Badge"
loading="lazy"
/>

<div className="flex flex-col gap-2">
<div className="flex gap-2 items-center">
<Select
onValueChange={(newValue) => {
setBadgeType(newValue);
}}
defaultValue={badgeType}
>
<SelectTrigger className="w-[100px]">
<SelectValue placeholder="Select mode" />
</SelectTrigger>
<SelectContent>
<SelectItem value="markdown">Markdown</SelectItem>
<SelectItem value="html">HTML</SelectItem>
</SelectContent>
</Select>
</div>

<CopyButton
text={
badgeType === 'markdown'
? `![IndoGitHubers-badge](https://indogithubers-badge.vercel.app/badge?username=${username})`
: ` <img src="https://indogithubers-badge.vercel.app/badge?username=${username}" alt="IndoGitHubers Badge">`
}
fullWidth
withInput={true}
withLabel
/>
</div>

<Alert>
<InfoIcon className="h-4 w-4" />
<AlertTitle>Additional parameters</AlertTitle>
<AlertDescription className="mt-2">
<ul className="list-disc list-outside grid gap-1">
<li>
<code className="bg-green-100 text-green-600 px-1 rounded font-mono">
style
</code>{' '}
values that you can use are: "social", "plastic", "flat",
"flat-square", "for-the-badge"
</li>
<li>
<code className="bg-green-100 text-green-600 px-1 rounded font-mono">
color
</code>{' '}
values that you can use, such as "red", "yellow", "00FF00",
"D3EB30", etc
</li>
</ul>
</AlertDescription>
</Alert>
</div>
</div>
</div>
);
};

0 comments on commit ca1e689

Please sign in to comment.