mirror of
https://github.com/tale/headplane.git
synced 2026-07-27 00:08:14 +00:00
feat: separate code snippets and code blocks for copyable snippets
This commit is contained in:
@@ -0,0 +1,34 @@
|
||||
import { Copy } from "lucide-react";
|
||||
|
||||
import cn from "~/utils/cn";
|
||||
import toast from "~/utils/toast";
|
||||
|
||||
export interface CodeBlockProps {
|
||||
children: string;
|
||||
className?: string;
|
||||
}
|
||||
|
||||
export default function CodeBlock({ children, className }: CodeBlockProps) {
|
||||
const text = children.trim();
|
||||
|
||||
return (
|
||||
<button
|
||||
type="button"
|
||||
className={cn(
|
||||
"w-full cursor-pointer rounded-md bg-mist-100 text-left dark:bg-mist-800",
|
||||
"hover:bg-mist-200 dark:hover:bg-mist-700 transition-colors",
|
||||
className,
|
||||
)}
|
||||
onClick={async () => {
|
||||
await navigator.clipboard.writeText(text);
|
||||
toast("Copied to clipboard");
|
||||
}}
|
||||
>
|
||||
<code className="block px-3 pt-2 pb-1 text-sm break-all">{text}</code>
|
||||
<span className="mt-0.5 flex items-center gap-1 px-3 pb-2 text-xs text-mist-500 dark:text-mist-400">
|
||||
<Copy className="size-3" />
|
||||
Click to copy
|
||||
</span>
|
||||
</button>
|
||||
);
|
||||
}
|
||||
+4
-36
@@ -1,51 +1,19 @@
|
||||
import { Check, Copy } from "lucide-react";
|
||||
import { HTMLProps } from "react";
|
||||
|
||||
import cn from "~/utils/cn";
|
||||
import toast from "~/utils/toast";
|
||||
|
||||
export interface CodeProps extends HTMLProps<HTMLSpanElement> {
|
||||
isCopyable?: boolean;
|
||||
export interface CodeProps {
|
||||
children: string | string[] | number;
|
||||
className?: string;
|
||||
}
|
||||
|
||||
export default function Code({ isCopyable, children, className }: CodeProps) {
|
||||
export default function Code({ children, className }: CodeProps) {
|
||||
return (
|
||||
<code
|
||||
className={cn(
|
||||
"bg-mist-100 dark:bg-mist-800 px-1 py-0.5 font-mono",
|
||||
"rounded-sm focus-within:outline-hidden focus-within:ring-2",
|
||||
isCopyable && "relative pr-7",
|
||||
"bg-mist-100 dark:bg-mist-800 px-1.5 py-0.5 font-mono rounded-sm text-[0.875em]",
|
||||
className,
|
||||
)}
|
||||
>
|
||||
{children}
|
||||
{isCopyable && (
|
||||
<button
|
||||
className="absolute right-0 bottom-0"
|
||||
onClick={async (event) => {
|
||||
const text = Array.isArray(children) ? children.join("") : String(children);
|
||||
|
||||
const svgs = event.currentTarget.querySelectorAll("svg");
|
||||
for (const svg of svgs) {
|
||||
svg.toggleAttribute("data-copied", true);
|
||||
}
|
||||
|
||||
await navigator.clipboard.writeText(text);
|
||||
toast("Copied to clipboard");
|
||||
|
||||
setTimeout(() => {
|
||||
for (const svg of svgs) {
|
||||
svg.toggleAttribute("data-copied", false);
|
||||
}
|
||||
}, 1000);
|
||||
}}
|
||||
type="button"
|
||||
>
|
||||
<Check className="hidden h-4.5 w-4.5 p-1 data-copied:block" />
|
||||
<Copy className="block h-4.5 w-4.5 p-1 data-copied:hidden" />
|
||||
</button>
|
||||
)}
|
||||
</code>
|
||||
);
|
||||
}
|
||||
|
||||
+3
-25
@@ -1,4 +1,4 @@
|
||||
import { Check, Copy } from "lucide-react";
|
||||
import { Check } from "lucide-react";
|
||||
import { redirect } from "react-router";
|
||||
|
||||
import androidSvg from "~/assets/android.svg";
|
||||
@@ -6,14 +6,13 @@ import iosSvg from "~/assets/ios.svg";
|
||||
import linuxSvg from "~/assets/linux.svg";
|
||||
import macosSvg from "~/assets/macos.svg";
|
||||
import windowsSvg from "~/assets/windows.svg";
|
||||
import Button from "~/components/button";
|
||||
import Card from "~/components/card";
|
||||
import CodeBlock from "~/components/code-block";
|
||||
import Link from "~/components/link";
|
||||
import LinkAccount from "~/layout/link-account";
|
||||
import { usersResource } from "~/server/headscale/live-store";
|
||||
import { Capabilities } from "~/server/web/roles";
|
||||
import cn from "~/utils/cn";
|
||||
import toast from "~/utils/toast";
|
||||
import { getUserDisplayName } from "~/utils/user";
|
||||
|
||||
import type { Route } from "./+types/home";
|
||||
@@ -152,28 +151,7 @@ export default function Home({ loaderData }: Route.ComponentProps) {
|
||||
<img alt="Linux" className="w-4 dark:invert" src={linuxSvg} />
|
||||
<span className="text-sm font-medium">Linux</span>
|
||||
</div>
|
||||
<div className="mt-2 flex items-center gap-2">
|
||||
<code
|
||||
className={cn(
|
||||
"flex-1 rounded-md px-3 py-2 text-xs h-8",
|
||||
"bg-mist-100 dark:bg-mist-800",
|
||||
)}
|
||||
>
|
||||
curl -fsSL https://tailscale.com/install.sh | sh
|
||||
</code>
|
||||
<Button
|
||||
className="h-8 p-1 px-2"
|
||||
variant="ghost"
|
||||
onClick={async () => {
|
||||
await navigator.clipboard.writeText(
|
||||
"curl -fsSL https://tailscale.com/install.sh | sh",
|
||||
);
|
||||
toast("Copied to clipboard");
|
||||
}}
|
||||
>
|
||||
<Copy className="size-4" />
|
||||
</Button>
|
||||
</div>
|
||||
<CodeBlock className="mt-2">curl -fsSL https://tailscale.com/install.sh | sh</CodeBlock>
|
||||
<p className="mt-1 text-xs text-mist-500 dark:text-mist-400">
|
||||
<Link
|
||||
external
|
||||
|
||||
@@ -3,7 +3,7 @@ import { Computer, FileKey2 } from "lucide-react";
|
||||
import { useState } from "react";
|
||||
import { useNavigate } from "react-router";
|
||||
|
||||
import Code from "~/components/code";
|
||||
import CodeBlock from "~/components/code-block";
|
||||
import Dialog, { DialogPanel } from "~/components/dialog";
|
||||
import Input from "~/components/input";
|
||||
import { Menu, MenuContent, MenuItem, MenuTrigger } from "~/components/menu";
|
||||
@@ -36,10 +36,8 @@ export default function NewMachine(data: NewMachineProps) {
|
||||
<Dialog isOpen={pushDialog} onOpenChange={setPushDialog}>
|
||||
<DialogPanel isDisabled={!form.canSubmit}>
|
||||
<Title>Register Machine Key</Title>
|
||||
<Text className="mb-4">
|
||||
The machine key is given when you run{" "}
|
||||
<Code isCopyable>tailscale up --login-server={data.server}</Code> on your device.
|
||||
</Text>
|
||||
<Text>The machine key is given when you run the following command on your device:</Text>
|
||||
<CodeBlock className="mb-4">{`tailscale up --login-server=${data.server}`}</CodeBlock>
|
||||
<input name="action_id" type="hidden" value="register" />
|
||||
<Input
|
||||
{...form.field("register_key")}
|
||||
|
||||
@@ -2,7 +2,7 @@ import { useEffect, useRef, useState } from "react";
|
||||
import { useFetcher } from "react-router";
|
||||
|
||||
import Button from "~/components/button";
|
||||
import Code from "~/components/code";
|
||||
import CodeBlock from "~/components/code-block";
|
||||
import Dialog, { DialogPanel } from "~/components/dialog";
|
||||
import Input from "~/components/input";
|
||||
import Link from "~/components/link";
|
||||
@@ -12,7 +12,6 @@ import Switch from "~/components/switch";
|
||||
import Text from "~/components/text";
|
||||
import Title from "~/components/title";
|
||||
import type { User } from "~/types";
|
||||
import toast from "~/utils/toast";
|
||||
import { getUserDisplayName } from "~/utils/user";
|
||||
|
||||
interface AddAuthKeyProps {
|
||||
@@ -95,23 +94,11 @@ export default function AddAuthKey({
|
||||
<DialogPanel variant="unactionable">
|
||||
<Title>Pre-auth key created</Title>
|
||||
<Text>Copy this key now. You will not be able to see the full key again.</Text>
|
||||
<div className="mt-4 flex items-center gap-2 rounded-lg bg-mist-100 px-3 py-2 dark:bg-mist-800">
|
||||
<code className="min-w-0 flex-1 truncate font-mono text-sm">{createdKey}</code>
|
||||
<Button
|
||||
className="shrink-0"
|
||||
onClick={async () => {
|
||||
await navigator.clipboard.writeText(createdKey);
|
||||
toast("Copied key to clipboard");
|
||||
}}
|
||||
variant="light"
|
||||
>
|
||||
Copy
|
||||
</Button>
|
||||
</div>
|
||||
<CodeBlock className="mt-4">{createdKey}</CodeBlock>
|
||||
<Text className="mt-4 text-sm">To register a device with this key:</Text>
|
||||
<Code isCopyable className="mt-1 block text-sm">
|
||||
<CodeBlock className="mt-1">
|
||||
{`tailscale up --login-server=${url} --authkey ${createdKey}`}
|
||||
</Code>
|
||||
</CodeBlock>
|
||||
</DialogPanel>
|
||||
) : (
|
||||
<DialogPanel
|
||||
|
||||
Reference in New Issue
Block a user