diff --git a/app/components/EquivalentCliCommand.tsx b/app/components/CopyCode.tsx similarity index 57% rename from app/components/EquivalentCliCommand.tsx rename to app/components/CopyCode.tsx index 98c2eb6409..63addffa1b 100644 --- a/app/components/EquivalentCliCommand.tsx +++ b/app/components/CopyCode.tsx @@ -5,7 +5,7 @@ * * Copyright Oxide Computer Company */ -import { useState } from 'react' +import { useState, type ReactNode } from 'react' import { Success12Icon } from '@oxide/design-system/icons/react' @@ -13,7 +13,22 @@ import { Button } from '~/ui/lib/Button' import { Modal } from '~/ui/lib/Modal' import { useTimeout } from '~/ui/lib/use-timeout' -export function EquivalentCliCommand({ command }: { command: string }) { +type CopyCodeProps = { + code: string + modalButtonText: string + copyButtonText: string + modalTitle: string + /** rendered code */ + children?: ReactNode +} + +export function CopyCode({ + code, + modalButtonText, + copyButtonText, + modalTitle, + children, +}: CopyCodeProps) { const [isOpen, setIsOpen] = useState(false) const [hasCopied, setHasCopied] = useState(false) @@ -24,7 +39,7 @@ export function EquivalentCliCommand({ command }: { command: string }) { useTimeout(() => setHasCopied(false), hasCopied ? 2000 : null) const handleCopy = () => { - window.navigator.clipboard.writeText(command).then(() => { + window.navigator.clipboard.writeText(code).then(() => { setHasCopied(true) }) } @@ -32,13 +47,12 @@ export function EquivalentCliCommand({ command }: { command: string }) { return ( <> - +
-            
$
- {command} + {children}
- {/* use of invisible keeps button the same size in both states */} - Copy command + {copyButtonText} ) } + +type EquivProps = { project: string; instance: string } + +export function EquivalentCliCommand({ project, instance }: EquivProps) { + const cmdParts = [ + 'oxide instance serial console', + `--project ${project}`, + `--instance ${instance}`, + ] + + return ( + +
$
+ {cmdParts.join(' \\\n')} +
+ ) +} diff --git a/app/components/form/FullPageForm.tsx b/app/components/form/FullPageForm.tsx index 2be6f31b07..d6090c0bf9 100644 --- a/app/components/form/FullPageForm.tsx +++ b/app/components/form/FullPageForm.tsx @@ -126,7 +126,7 @@ const ConfirmNavigation = ({ blocker }: { blocker: Blocker }) => ( isOpen={blocker.state === 'blocked'} onDismiss={() => blocker.reset?.()} title="Confirm navigation" - narrow + width="narrow" > Are you sure you want to leave this page? diff --git a/app/components/form/SideModalForm.tsx b/app/components/form/SideModalForm.tsx index 4cc2a87787..3e353052e6 100644 --- a/app/components/form/SideModalForm.tsx +++ b/app/components/form/SideModalForm.tsx @@ -151,7 +151,7 @@ export function SideModalForm({ isOpen onDismiss={() => setShowNavGuard(false)} title="Confirm navigation" - narrow + width="narrow" overlay={false} > diff --git a/app/pages/project/instances/instance/SerialConsolePage.tsx b/app/pages/project/instances/instance/SerialConsolePage.tsx index 323f87d787..c66f0b50d4 100644 --- a/app/pages/project/instances/instance/SerialConsolePage.tsx +++ b/app/pages/project/instances/instance/SerialConsolePage.tsx @@ -18,12 +18,11 @@ import { } from '@oxide/api' import { PrevArrow12Icon } from '@oxide/design-system/icons/react' -import { EquivalentCliCommand } from '~/components/EquivalentCliCommand' +import { EquivalentCliCommand } from '~/components/CopyCode' import { InstanceStateBadge } from '~/components/StateBadge' import { getInstanceSelector, useInstanceSelector } from '~/hooks/use-params' import { Badge, type BadgeColor } from '~/ui/lib/Badge' import { Spinner } from '~/ui/lib/Spinner' -import { cliCmd } from '~/util/cli-cmd' import { pb } from '~/util/path-builder' const Terminal = lazy(() => import('~/components/Terminal')) @@ -159,7 +158,7 @@ export function Component() {
- +
diff --git a/app/pages/project/instances/instance/tabs/ConnectTab.tsx b/app/pages/project/instances/instance/tabs/ConnectTab.tsx index aca98be654..ca0e518d42 100644 --- a/app/pages/project/instances/instance/tabs/ConnectTab.tsx +++ b/app/pages/project/instances/instance/tabs/ConnectTab.tsx @@ -9,12 +9,11 @@ import { Link, type LoaderFunctionArgs } from 'react-router' import { apiQueryClient, usePrefetchedApiQuery } from '~/api' -import { EquivalentCliCommand } from '~/components/EquivalentCliCommand' +import { EquivalentCliCommand } from '~/components/CopyCode' import { getInstanceSelector, useInstanceSelector } from '~/hooks/use-params' import { buttonStyle } from '~/ui/lib/Button' import { InlineCode } from '~/ui/lib/InlineCode' import { LearnMore, SettingsGroup } from '~/ui/lib/SettingsGroup' -import { cliCmd } from '~/util/cli-cmd' import { links } from '~/util/links' import { pb } from '~/util/path-builder' @@ -51,7 +50,7 @@ export function Component() {
- + = { + narrow: 'w-full max-w-[24rem]', + medium: 'w-full max-w-[28rem]', + free: 'min-w-[24rem] max-w-[48rem]', // give it a big max just to be safe +} + export type ModalProps = { title: string isOpen: boolean children?: React.ReactNode onDismiss: () => void - /** Default false. Only needed in a couple of spots. */ - narrow?: true + /** Default medium. Only needed in a couple of spots. */ + width?: Width /** Default true. We only need to hide it for the rare case of modal on top of modal. */ overlay?: boolean } @@ -38,7 +46,7 @@ export function Modal({ onDismiss, title, isOpen, - narrow, + width = 'medium', overlay = true, }: ModalProps) { return ( @@ -66,8 +74,8 @@ export function Modal({ animate={{ x: '-50%', y: '-50%' }} transition={{ type: 'spring', duration: 0.3, bounce: 0 }} className={cn( - 'pointer-events-auto fixed left-1/2 top-[min(50%,500px)] z-modal m-0 flex max-h-[min(800px,80vh)] w-full flex-col justify-between rounded-lg border p-0 bg-raise border-secondary elevation-2', - narrow ? 'max-w-[24rem]' : 'max-w-[28rem]' + 'pointer-events-auto fixed left-1/2 top-[min(50%,500px)] z-modal m-0 flex max-h-[min(800px,80vh)] flex-col justify-between rounded-lg border p-0 bg-raise border-secondary elevation-2', + widthClass[width] )} >