Skip to content

Commit 00204c4

Browse files
committed
🎨 Separate copy utilities + improve copy library
1 parent 9f160ff commit 00204c4

File tree

6 files changed

+204
-148
lines changed

6 files changed

+204
-148
lines changed

website/app/components/card/cardItem.tsx

Lines changed: 35 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,25 +1,56 @@
11
import type { iIcons } from "@/data/svgs";
22

3+
import { cn } from "@/utils";
4+
5+
import CopySource from "@/components/card/copySource";
6+
import CopyShadcnCommand from "@/components/card/copyShadcnCommand";
7+
import CopyLibraryImport from "@/components/card/copyLibraryImport";
38
import OpenWithV0 from "@/components/card/openWithv0";
4-
import CopyIconActions from "@/components/card/copyIcon";
59

610
interface iCard extends iIcons {
711
isFolder: boolean;
812
iconSize: number;
913
}
1014

1115
const Card = (props: iCard) => {
16+
const cardItemSize = 16;
1217
return (
13-
<div className="group flex flex-col items-center justify-center rounded-lg border border-zinc-200 px-4 pt-4 pb-2 dark:border-zinc-800">
18+
<div
19+
className={cn(
20+
"px-4 pt-4 pb-2",
21+
"relative overflow-hidden",
22+
"group flex flex-col items-center justify-center space-y-2.5 rounded-md",
23+
"border border-zinc-200 dark:border-zinc-800",
24+
)}
25+
>
1426
<props.icon width={props.iconSize} height={props.iconSize} />
15-
<p className="mt-2 mb-1.5 text-sm tracking-tight text-black dark:text-white">
27+
<p className="text-sm tracking-tight text-black dark:text-white">
1628
{props.name}
1729
</p>
30+
<div
31+
className={cn(
32+
"absolute top-0 right-0",
33+
"rounded-bl-md border-b border-l border-zinc-200 dark:border-zinc-800",
34+
)}
35+
>
36+
<CopySource
37+
iconSize={14}
38+
itemName={props.name}
39+
isFolder={props.isFolder}
40+
ItemIcon={props.icon}
41+
/>
42+
</div>
1843
<div className="flex items-center space-x-1">
19-
<CopyIconActions
44+
<CopyLibraryImport
45+
iconSize={cardItemSize}
2046
itemName={props.name}
2147
ItemIcon={props.icon}
48+
/>
49+
<CopyShadcnCommand
50+
iconSize={cardItemSize}
51+
itemName={props.name}
2252
isFolder={props.isFolder}
53+
ItemIcon={props.icon}
2354
/>
2455
<OpenWithV0
2556
itemName={props.name.charAt(0).toLowerCase() + props.name.slice(1)}

website/app/components/card/copyIcon.tsx

Lines changed: 0 additions & 143 deletions
This file was deleted.
Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,40 @@
1+
import type { FC, SVGProps } from "react";
2+
3+
import { clipboard } from "@/utils";
4+
import { toast } from "@pheralb/toast";
5+
import { Button } from "@/ui/button";
6+
import { CopyIcon } from "lucide-react";
7+
8+
interface CopyLibraryImportProps {
9+
itemName: string;
10+
iconSize: number;
11+
ItemIcon: FC<SVGProps<SVGSVGElement>>;
12+
}
13+
14+
const CopyLibraryImport = ({
15+
itemName,
16+
iconSize,
17+
ItemIcon,
18+
}: CopyLibraryImportProps) => {
19+
const handleCopyFromLibrary = async () => {
20+
const code = `import { ${itemName} } from "@react-symbols/icons";`;
21+
await clipboard(code);
22+
toast.success({
23+
text: "Copied library import",
24+
icon: <ItemIcon width={24} height={24} />,
25+
});
26+
};
27+
28+
return (
29+
<Button
30+
title="Copy library import"
31+
variant="ghost"
32+
size="icon"
33+
onClick={handleCopyFromLibrary}
34+
>
35+
<CopyIcon size={iconSize} />
36+
</Button>
37+
);
38+
};
39+
40+
export default CopyLibraryImport;
Lines changed: 46 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,46 @@
1+
import type { FC, SVGProps } from "react";
2+
3+
import { clipboard } from "@/utils";
4+
import { toast } from "@pheralb/toast";
5+
import { appConfig } from "@/config";
6+
import { Button } from "@/ui/button";
7+
import { Shadcnui } from "@/ui/icons/svgl";
8+
9+
interface CopyShadcnCommandProps {
10+
itemName: string;
11+
iconSize: number;
12+
isFolder?: boolean;
13+
ItemIcon: FC<SVGProps<SVGSVGElement>>;
14+
}
15+
16+
const CopyShadcnCommand = ({
17+
itemName,
18+
iconSize,
19+
isFolder,
20+
ItemIcon,
21+
}: CopyShadcnCommandProps) => {
22+
const handleCopyShadcnCommand = async () => {
23+
const itemNameLower = itemName.charAt(0).toLowerCase() + itemName.slice(1);
24+
const makeCommand = !isFolder
25+
? `${appConfig.shadcnCommand} ${appConfig.registryUrl}${itemNameLower}.json`
26+
: `${appConfig.shadcnCommand} ${appConfig.registryUrl}folders/${itemNameLower}.json`;
27+
28+
await clipboard(makeCommand);
29+
toast.success({
30+
text: "Copied shadcn/ui command",
31+
icon: <ItemIcon width={24} height={24} />,
32+
});
33+
};
34+
return (
35+
<Button
36+
title="Copy shadcn/ui command"
37+
variant="ghost"
38+
size="icon"
39+
onClick={handleCopyShadcnCommand}
40+
>
41+
<Shadcnui width={iconSize} height={iconSize} />
42+
</Button>
43+
);
44+
};
45+
46+
export default CopyShadcnCommand;
Lines changed: 82 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,82 @@
1+
import { useState, type FC, type SVGProps } from "react";
2+
3+
import axios from "axios";
4+
import { appConfig } from "@/config";
5+
import { clipboard, cn } from "@/utils";
6+
import { toast } from "@pheralb/toast";
7+
import { CodeIcon, LoaderIcon } from "lucide-react";
8+
9+
interface CopySourceProps {
10+
itemName: string;
11+
iconSize: number;
12+
isFolder?: boolean;
13+
ItemIcon: FC<SVGProps<SVGSVGElement>>;
14+
}
15+
16+
const CopySource = ({
17+
itemName,
18+
iconSize,
19+
isFolder,
20+
ItemIcon,
21+
}: CopySourceProps) => {
22+
const [isLoading, setLoading] = useState<boolean>(false);
23+
24+
const handleCopySource = async () => {
25+
setLoading(true);
26+
const itemNameLower = itemName.charAt(0).toLowerCase() + itemName.slice(1);
27+
const jsonUrl = !isFolder
28+
? `${appConfig.registryUrl}${itemNameLower}.json`
29+
: `${appConfig.registryUrl}folders/${itemNameLower}.json`;
30+
31+
try {
32+
const response = await axios.get(jsonUrl);
33+
const json = response.data;
34+
35+
if (!json.files || !json.files.length) {
36+
console.error(
37+
"⚠️ Error fetching or copying source code: ",
38+
"No files found.",
39+
);
40+
}
41+
42+
const file = json.files[0];
43+
let content = file.content as string;
44+
content = content.replace(/\r\n/g, "\n");
45+
46+
await clipboard(content);
47+
toast.success({
48+
text: "Copied source code",
49+
icon: <ItemIcon width={24} height={24} />,
50+
});
51+
setLoading(false);
52+
} catch (error) {
53+
console.error("⚠️ Error fetching or copying source code: ", error);
54+
toast.error({
55+
text: "Failed to copy source code. Please try again.",
56+
});
57+
setLoading(false);
58+
}
59+
};
60+
61+
return (
62+
<button
63+
title="Copy source code"
64+
onClick={handleCopySource}
65+
disabled={isLoading}
66+
className={cn(
67+
"cursor-pointer transition-colors",
68+
isLoading && "cursor-default",
69+
"p-1.5 text-zinc-500 dark:text-zinc-500",
70+
"hover:text-zinc-950 dark:hover:text-zinc-50",
71+
)}
72+
>
73+
{isLoading ? (
74+
<LoaderIcon size={iconSize} className="animate-spin" />
75+
) : (
76+
<CodeIcon size={iconSize} />
77+
)}
78+
</button>
79+
);
80+
};
81+
82+
export default CopySource;

website/app/components/card/openWithv0.tsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,7 @@ interface OpenWithV0Props {
1212
const OpenWithV0 = ({ itemName, isFolder }: OpenWithV0Props) => {
1313
return (
1414
<ExternalLink
15-
title="Open with V0"
15+
title="Open with v0"
1616
href={
1717
!isFolder
1818
? `${appConfig.v0URL}${appConfig.registryUrl}${itemName}.json`

0 commit comments

Comments
 (0)