|
1 | 1 | import { useEffect, useState } from "react";
|
2 | 2 | import { type Resource } from "~/lib/resources.server";
|
3 |
| -import { Link, useSearchParams } from "@remix-run/react"; |
| 3 | +import { transformNpmCommand } from "~/lib/transformNpmCommand"; |
| 4 | +import type { PackageManager } from "~/lib/transformNpmCommand"; |
| 5 | +import { DetailsMenu, DetailsPopup } from "./details-menu"; |
| 6 | + |
| 7 | +import { Form, Link, useSearchParams, useSubmit } from "@remix-run/react"; |
4 | 8 | import cx from "clsx";
|
5 | 9 | import iconsHref from "~/icons.svg";
|
6 | 10 |
|
@@ -67,6 +71,13 @@ export function InitCodeblock({
|
67 | 71 | let [npxOrNpmMaybe, ...otherCode] = initCommand.trim().split(" ");
|
68 | 72 | let [copied, setCopied] = useState(false);
|
69 | 73 |
|
| 74 | + function handleCopied(copied: boolean, packageManager: PackageManager) { |
| 75 | + setCopied(copied); |
| 76 | + navigator.clipboard.writeText( |
| 77 | + transformNpmCommand(npxOrNpmMaybe, otherCode.join(" "), packageManager), |
| 78 | + ); |
| 79 | + } |
| 80 | + |
70 | 81 | // Reset copied state after 4 seconds
|
71 | 82 | useEffect(() => {
|
72 | 83 | if (copied) {
|
@@ -106,30 +117,101 @@ export function InitCodeblock({
|
106 | 117 | </code>
|
107 | 118 | </pre>
|
108 | 119 |
|
109 |
| - <button |
110 |
| - type="button" |
111 |
| - onClick={() => { |
112 |
| - setCopied(true); |
113 |
| - navigator.clipboard.writeText(initCommand); |
114 |
| - }} |
115 |
| - data-code-block-copy |
| 120 | + <CopyCodeBlock setCopied={handleCopied} copied={copied} /> |
| 121 | + </div> |
| 122 | + ); |
| 123 | +} |
| 124 | + |
| 125 | +type CopyCodeBlockProps = { |
| 126 | + copied: boolean; |
| 127 | + setCopied: (copied: boolean, packageManager: PackageManager) => void; |
| 128 | +}; |
| 129 | + |
| 130 | +function CopyCodeBlock({ copied, setCopied }: CopyCodeBlockProps) { |
| 131 | + const [isMenuOpen, setIsMenuOpen] = useState(false); |
| 132 | + const [searchParams] = useSearchParams(); |
| 133 | + |
| 134 | + return ( |
| 135 | + <DetailsMenu |
| 136 | + className="absolute right-4 top-0 !opacity-100" |
| 137 | + data-copied={copied} |
| 138 | + onToggle={() => setIsMenuOpen((oldValue) => !oldValue)} |
| 139 | + > |
| 140 | + <summary |
| 141 | + className="_no-triangle absolute top-0 grid" |
116 | 142 | data-copied={copied}
|
117 |
| - className="outline-none" |
118 | 143 | >
|
119 |
| - {/* had to put these here instead of as a mask so we could add an opacity */} |
120 |
| - <svg |
121 |
| - aria-hidden |
122 |
| - className="h-5 w-5 text-gray-500 hover:text-black dark:text-gray-400 dark:hover:text-gray-100" |
123 |
| - viewBox="0 0 24 24" |
| 144 | + <span |
| 145 | + data-code-block-copy |
| 146 | + data-copied={copied} |
| 147 | + className={`absolute right-0 top-0 opacity-0 hover:opacity-100 ${copied || isMenuOpen ? "!opacity-100" : ""}`} |
124 | 148 | >
|
125 |
| - {copied ? ( |
126 |
| - <use href={`${iconsHref}#check-mark`} /> |
127 |
| - ) : ( |
128 |
| - <use href={`${iconsHref}#copy`} /> |
129 |
| - )} |
130 |
| - </svg> |
131 |
| - <span className="sr-only">Copy code to clipboard</span> |
132 |
| - </button> |
133 |
| - </div> |
| 149 | + <svg |
| 150 | + aria-hidden |
| 151 | + className="h-5 w-5 text-gray-500 hover:text-black dark:text-gray-400 dark:hover:text-gray-100" |
| 152 | + viewBox="0 0 24 24" |
| 153 | + > |
| 154 | + {copied ? ( |
| 155 | + <use href={`${iconsHref}#check-mark`} /> |
| 156 | + ) : ( |
| 157 | + <use href={`${iconsHref}#copy`} /> |
| 158 | + )} |
| 159 | + </svg> |
| 160 | + <span className="sr-only">Copy code to clipboard</span> |
| 161 | + </span> |
| 162 | + </summary> |
| 163 | + <DetailsPopup className="-bottom-28" childrenClassName="!w-20"> |
| 164 | + <Form preventScrollReset replace className="flex flex-col gap-px"> |
| 165 | + <input |
| 166 | + type="hidden" |
| 167 | + name="category" |
| 168 | + value={searchParams.get("category") || ""} |
| 169 | + /> |
| 170 | + |
| 171 | + <PackageManagerButton |
| 172 | + packageManager="npm" |
| 173 | + setCopied={(copied) => setCopied(copied, "npm")} |
| 174 | + /> |
| 175 | + <PackageManagerButton |
| 176 | + packageManager="yarn" |
| 177 | + setCopied={(copied) => setCopied(copied, "yarn")} |
| 178 | + /> |
| 179 | + <PackageManagerButton |
| 180 | + packageManager="pnpm" |
| 181 | + setCopied={(copied) => setCopied(copied, "pnpm")} |
| 182 | + /> |
| 183 | + <PackageManagerButton |
| 184 | + packageManager="bun" |
| 185 | + setCopied={(copied) => setCopied(copied, "bun")} |
| 186 | + /> |
| 187 | + </Form> |
| 188 | + </DetailsPopup> |
| 189 | + </DetailsMenu> |
| 190 | + ); |
| 191 | +} |
| 192 | + |
| 193 | +type PackageManagerButtonProps = { |
| 194 | + packageManager: PackageManager; |
| 195 | + setCopied: (copied: boolean) => void; |
| 196 | +}; |
| 197 | + |
| 198 | +function PackageManagerButton({ |
| 199 | + packageManager, |
| 200 | + setCopied, |
| 201 | +}: PackageManagerButtonProps) { |
| 202 | + const submit = useSubmit(); |
| 203 | + return ( |
| 204 | + <button |
| 205 | + className="rounded-sm hover:cursor-pointer hover:bg-gray-50" |
| 206 | + type="submit" |
| 207 | + onClick={(e) => { |
| 208 | + submit({ |
| 209 | + preventScrollReset: false, |
| 210 | + }); |
| 211 | + setCopied(true); |
| 212 | + }} |
| 213 | + > |
| 214 | + {packageManager} |
| 215 | + </button> |
134 | 216 | );
|
135 | 217 | }
|
0 commit comments