Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat(resources): add ability to choose package manager #173

Open
wants to merge 5 commits into
base: main
Choose a base branch
from
Open
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
21 changes: 21 additions & 0 deletions app/lib/transformNpmCommand.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
export type PackageManager = "npm" | "yarn" | "pnpm" | "bun";

export function transformNpmCommand(
prefix: string,
cmd: string,
packageManagerTarget: "yarn" | "bun" | "pnpm" | "npm",
) {
if (prefix === "npm") {
if (cmd.split(" ")[0] === "install" && packageManagerTarget === "yarn") {
return `${packageManagerTarget} ${cmd.replace("install", "add")}`;
}
return `${packageManagerTarget} ${cmd}`;
}
switch (packageManagerTarget) {
case "bun":
return `bunx ${cmd}`;
case "pnpm":
return `pnpm dlx ${cmd}`;
}
return `${prefix} ${cmd}`;
}
brookslybrand marked this conversation as resolved.
Show resolved Hide resolved
6 changes: 3 additions & 3 deletions app/routes/docs.$lang.$ref.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -268,7 +268,7 @@ function VersionSelect() {
</svg>
</summary>
<DetailsPopup>
<div className="flex flex-col gap-px">
<div className="flex w-40 flex-col gap-px">
<VersionsLabel label="Branches" />
{branches.map((branch) => {
return (
Expand Down Expand Up @@ -390,7 +390,7 @@ function ColorSchemeToggle() {
replace
action="/_actions/color-scheme"
method="post"
className="flex flex-col gap-px"
className="flex w-40 flex-col gap-px"
>
<input
type="hidden"
Expand Down Expand Up @@ -547,7 +547,7 @@ function HeaderMenuMobile({ className = "" }: { className: string }) {
</svg>
</summary>
<DetailsPopup>
<div className="flex flex-col">
<div className="flex w-40 flex-col">
<HeaderMenuLink to="/docs">Docs</HeaderMenuLink>
<HeaderMenuLink to="/blog">Blog</HeaderMenuLink>
<HeaderMenuLink to="/showcase">Showcase</HeaderMenuLink>
Expand Down
2 changes: 1 addition & 1 deletion app/styles/resources.css
Original file line number Diff line number Diff line change
Expand Up @@ -45,7 +45,7 @@
}

&:hover [data-code-block-copy],
& [data-code-block-copy]:focus {
& [data-code-block-copy]:focus-within {
@apply opacity-100;
}

Expand Down
12 changes: 9 additions & 3 deletions app/ui/details-menu.tsx
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import { forwardRef, useState, useRef, useEffect } from "react";
import { useLocation, useNavigation } from "@remix-run/react";
import cx from "clsx";

/**
* An enhanced `<details>` component that's intended to be used as a menu (a bit
Expand Down Expand Up @@ -77,10 +78,15 @@ export const DetailsMenu = forwardRef<
});
DetailsMenu.displayName = "DetailsMenu";

export function DetailsPopup({ children }: { children: React.ReactNode }) {
type DetailsPopupProps = {
children: React.ReactNode;
className?: string;
};

export function DetailsPopup({ children, className }: DetailsPopupProps) {
return (
<div className="absolute right-0 z-20 md:left-0">
<div className="relative top-1 w-40 rounded-md border border-gray-100 bg-white p-1 shadow-sm dark:border-gray-800 dark:bg-gray-900 ">
<div className={cx("absolute right-0 z-20 min-w-max md:left-0", className)}>
<div className="relative top-1 rounded-md border border-gray-100 bg-white p-1 shadow-sm dark:border-gray-800 dark:bg-gray-900">
{children}
</div>
</div>
Expand Down
2 changes: 1 addition & 1 deletion app/ui/header.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -65,7 +65,7 @@ function HeaderMenuMobile({ className = "" }: { className: string }) {
</svg>
</summary>
<DetailsPopup>
<nav className="flex flex-col gap-2 px-2 py-2.5">
<nav className="flex w-40 flex-col gap-2 px-2 py-2.5">
<HeaderLink to="/docs/en/main">Docs</HeaderLink>
<HeaderLink to="/blog">Blog</HeaderLink>
<HeaderLink to="/showcase">Showcase</HeaderLink>
Expand Down
92 changes: 68 additions & 24 deletions app/ui/resources.tsx
Original file line number Diff line number Diff line change
@@ -1,5 +1,9 @@
import { useEffect, useState } from "react";
import { useEffect, useRef, useState } from "react";
import { type Resource } from "~/lib/resources.server";
import { transformNpmCommand } from "~/lib/transformNpmCommand";
import type { PackageManager } from "~/lib/transformNpmCommand";
import { DetailsMenu, DetailsPopup } from "./details-menu";

import { Link, useSearchParams } from "@remix-run/react";
import cx from "clsx";
import iconsHref from "~/icons.svg";
Expand Down Expand Up @@ -67,6 +71,13 @@ export function InitCodeblock({
let [npxOrNpmMaybe, ...otherCode] = initCommand.trim().split(" ");
let [copied, setCopied] = useState(false);

function handleCopy(packageManager: PackageManager) {
setCopied(true);
navigator.clipboard.writeText(
transformNpmCommand(npxOrNpmMaybe, otherCode.join(" "), packageManager),
);
}

// Reset copied state after 4 seconds
useEffect(() => {
if (copied) {
Expand Down Expand Up @@ -106,30 +117,63 @@ export function InitCodeblock({
</code>
</pre>

<button
type="button"
onClick={() => {
setCopied(true);
navigator.clipboard.writeText(initCommand);
}}
data-code-block-copy
<CopyCodeBlock copied={copied} onCopy={handleCopy} />
</div>
);
}

type CopyCodeBlockProps = {
copied: boolean;
onCopy: (packageManager: PackageManager) => void;
};

function CopyCodeBlock({ copied, onCopy }: CopyCodeBlockProps) {
const detailsRef = useRef<HTMLDetailsElement>(null);
return (
<DetailsMenu
className="absolute"
data-copied={copied}
data-code-block-copy
ref={detailsRef}
>
<summary
className="_no-triangle block outline-offset-2"
data-copied={copied}
className="outline-none"
>
{/* had to put these here instead of as a mask so we could add an opacity */}
<svg
aria-hidden
className="h-5 w-5 text-gray-500 hover:text-black dark:text-gray-400 dark:hover:text-gray-100"
viewBox="0 0 24 24"
>
{copied ? (
<use href={`${iconsHref}#check-mark`} />
) : (
<use href={`${iconsHref}#copy`} />
)}
</svg>
<span className="sr-only">Copy code to clipboard</span>
</button>
</div>
<span data-copied={copied}>
<svg
aria-hidden
className="h-5 w-5 text-gray-500 hover:text-black dark:text-gray-400 dark:hover:text-gray-100"
viewBox="0 0 24 24"
>
{copied ? (
<use href={`${iconsHref}#check-mark`} />
) : (
<use href={`${iconsHref}#copy`} />
)}
</svg>
<span className="sr-only">Copy code to clipboard</span>
</span>
</summary>
<div className="absolute right-0 w-28">
<DetailsPopup>
<div className="flex flex-col">
{(["npm", "yarn", "pnpm", "bun"] as const).map((packageManager) => (
<button
key={packageManager}
className="rounded-md p-1.5 text-left text-sm text-gray-700 hover:bg-blue-200/50 hover:text-black dark:text-gray-400 dark:hover:bg-blue-800/50 dark:hover:text-gray-100"
onClick={() => {
onCopy(packageManager);
// Close the details menu
detailsRef.current?.toggleAttribute("open");
}}
>
{packageManager}
</button>
))}
</div>
</DetailsPopup>
</div>
</DetailsMenu>
);
}