Skip to content

Commit c14b4af

Browse files
authored
minor: Abstract equivalent command modal into CopyCode (#2693)
abstract equivalent command modal into CopyCode
1 parent 7de4b11 commit c14b4af

File tree

6 files changed

+63
-22
lines changed

6 files changed

+63
-22
lines changed

app/components/EquivalentCliCommand.tsx renamed to app/components/CopyCode.tsx

Lines changed: 44 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -5,15 +5,30 @@
55
*
66
* Copyright Oxide Computer Company
77
*/
8-
import { useState } from 'react'
8+
import { useState, type ReactNode } from 'react'
99

1010
import { Success12Icon } from '@oxide/design-system/icons/react'
1111

1212
import { Button } from '~/ui/lib/Button'
1313
import { Modal } from '~/ui/lib/Modal'
1414
import { useTimeout } from '~/ui/lib/use-timeout'
1515

16-
export function EquivalentCliCommand({ command }: { command: string }) {
16+
type CopyCodeProps = {
17+
code: string
18+
modalButtonText: string
19+
copyButtonText: string
20+
modalTitle: string
21+
/** rendered code */
22+
children?: ReactNode
23+
}
24+
25+
export function CopyCode({
26+
code,
27+
modalButtonText,
28+
copyButtonText,
29+
modalTitle,
30+
children,
31+
}: CopyCodeProps) {
1732
const [isOpen, setIsOpen] = useState(false)
1833
const [hasCopied, setHasCopied] = useState(false)
1934

@@ -24,30 +39,28 @@ export function EquivalentCliCommand({ command }: { command: string }) {
2439
useTimeout(() => setHasCopied(false), hasCopied ? 2000 : null)
2540

2641
const handleCopy = () => {
27-
window.navigator.clipboard.writeText(command).then(() => {
42+
window.navigator.clipboard.writeText(code).then(() => {
2843
setHasCopied(true)
2944
})
3045
}
3146

3247
return (
3348
<>
3449
<Button variant="ghost" size="sm" className="ml-2" onClick={() => setIsOpen(true)}>
35-
Equivalent CLI Command
50+
{modalButtonText}
3651
</Button>
37-
<Modal isOpen={isOpen} onDismiss={handleDismiss} title="CLI command">
52+
<Modal isOpen={isOpen} onDismiss={handleDismiss} title={modalTitle} width="free">
3853
<Modal.Section>
3954
<pre className="flex w-full rounded border px-4 py-3 !normal-case !tracking-normal text-mono-md bg-default border-secondary">
40-
<div className="mr-2 select-none text-tertiary">$</div>
41-
{command}
55+
{children}
4256
</pre>
4357
</Modal.Section>
4458
<Modal.Footer
4559
onDismiss={handleDismiss}
4660
onAction={handleCopy}
4761
actionText={
4862
<>
49-
{/* use of invisible keeps button the same size in both states */}
50-
<span className={hasCopied ? 'invisible' : ''}>Copy command</span>
63+
<span className={hasCopied ? 'invisible' : ''}>{copyButtonText}</span>
5164
<span
5265
className={`absolute left-1/2 top-1/2 flex -translate-x-1/2 -translate-y-1/2 items-center ${
5366
hasCopied ? '' : 'invisible'
@@ -63,3 +76,25 @@ export function EquivalentCliCommand({ command }: { command: string }) {
6376
</>
6477
)
6578
}
79+
80+
type EquivProps = { project: string; instance: string }
81+
82+
export function EquivalentCliCommand({ project, instance }: EquivProps) {
83+
const cmdParts = [
84+
'oxide instance serial console',
85+
`--project ${project}`,
86+
`--instance ${instance}`,
87+
]
88+
89+
return (
90+
<CopyCode
91+
code={cmdParts.join(' ')}
92+
modalButtonText="Equivalent CLI Command"
93+
copyButtonText="Copy command"
94+
modalTitle="CLI command"
95+
>
96+
<div className="mr-2 select-none text-tertiary">$</div>
97+
{cmdParts.join(' \\\n')}
98+
</CopyCode>
99+
)
100+
}

app/components/form/FullPageForm.tsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -126,7 +126,7 @@ const ConfirmNavigation = ({ blocker }: { blocker: Blocker }) => (
126126
isOpen={blocker.state === 'blocked'}
127127
onDismiss={() => blocker.reset?.()}
128128
title="Confirm navigation"
129-
narrow
129+
width="narrow"
130130
>
131131
<Modal.Section>
132132
Are you sure you want to leave this page?

app/components/form/SideModalForm.tsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -151,7 +151,7 @@ export function SideModalForm<TFieldValues extends FieldValues>({
151151
isOpen
152152
onDismiss={() => setShowNavGuard(false)}
153153
title="Confirm navigation"
154-
narrow
154+
width="narrow"
155155
overlay={false}
156156
>
157157
<Modal.Section>

app/pages/project/instances/instance/SerialConsolePage.tsx

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -18,12 +18,11 @@ import {
1818
} from '@oxide/api'
1919
import { PrevArrow12Icon } from '@oxide/design-system/icons/react'
2020

21-
import { EquivalentCliCommand } from '~/components/EquivalentCliCommand'
21+
import { EquivalentCliCommand } from '~/components/CopyCode'
2222
import { InstanceStateBadge } from '~/components/StateBadge'
2323
import { getInstanceSelector, useInstanceSelector } from '~/hooks/use-params'
2424
import { Badge, type BadgeColor } from '~/ui/lib/Badge'
2525
import { Spinner } from '~/ui/lib/Spinner'
26-
import { cliCmd } from '~/util/cli-cmd'
2726
import { pb } from '~/util/path-builder'
2827

2928
const Terminal = lazy(() => import('~/components/Terminal'))
@@ -159,7 +158,7 @@ export function Component() {
159158
<div className="shrink-0 justify-between overflow-hidden border-t bg-default border-secondary empty:border-t-0">
160159
<div className="gutter flex h-20 items-center justify-between">
161160
<div>
162-
<EquivalentCliCommand command={cliCmd.serialConsole({ project, instance })} />
161+
<EquivalentCliCommand project={project} instance={instance} />
163162
</div>
164163

165164
<Badge color={statusColor[connectionStatus]}>

app/pages/project/instances/instance/tabs/ConnectTab.tsx

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -9,12 +9,11 @@
99
import { Link, type LoaderFunctionArgs } from 'react-router'
1010

1111
import { apiQueryClient, usePrefetchedApiQuery } from '~/api'
12-
import { EquivalentCliCommand } from '~/components/EquivalentCliCommand'
12+
import { EquivalentCliCommand } from '~/components/CopyCode'
1313
import { getInstanceSelector, useInstanceSelector } from '~/hooks/use-params'
1414
import { buttonStyle } from '~/ui/lib/Button'
1515
import { InlineCode } from '~/ui/lib/InlineCode'
1616
import { LearnMore, SettingsGroup } from '~/ui/lib/SettingsGroup'
17-
import { cliCmd } from '~/util/cli-cmd'
1817
import { links } from '~/util/links'
1918
import { pb } from '~/util/path-builder'
2019

@@ -51,7 +50,7 @@ export function Component() {
5150
<LearnMore text="Serial Console" href={links.serialConsoleDocs} />
5251
</div>
5352
<div className="flex gap-3">
54-
<EquivalentCliCommand command={cliCmd.serialConsole({ project, instance })} />
53+
<EquivalentCliCommand project={project} instance={instance} />
5554
<Link
5655
to={pb.serialConsole({ project, instance })}
5756
className={buttonStyle({ size: 'sm' })}

app/ui/lib/Modal.tsx

Lines changed: 13 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -18,13 +18,21 @@ import { Button } from './Button'
1818
import { DialogOverlay } from './DialogOverlay'
1919
import { ModalContext } from './modal-context'
2020

21+
type Width = 'narrow' | 'medium' | 'free'
22+
23+
const widthClass: Record<Width, string> = {
24+
narrow: 'w-full max-w-[24rem]',
25+
medium: 'w-full max-w-[28rem]',
26+
free: 'min-w-[24rem] max-w-[48rem]', // give it a big max just to be safe
27+
}
28+
2129
export type ModalProps = {
2230
title: string
2331
isOpen: boolean
2432
children?: React.ReactNode
2533
onDismiss: () => void
26-
/** Default false. Only needed in a couple of spots. */
27-
narrow?: true
34+
/** Default medium. Only needed in a couple of spots. */
35+
width?: Width
2836
/** Default true. We only need to hide it for the rare case of modal on top of modal. */
2937
overlay?: boolean
3038
}
@@ -38,7 +46,7 @@ export function Modal({
3846
onDismiss,
3947
title,
4048
isOpen,
41-
narrow,
49+
width = 'medium',
4250
overlay = true,
4351
}: ModalProps) {
4452
return (
@@ -66,8 +74,8 @@ export function Modal({
6674
animate={{ x: '-50%', y: '-50%' }}
6775
transition={{ type: 'spring', duration: 0.3, bounce: 0 }}
6876
className={cn(
69-
'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',
70-
narrow ? 'max-w-[24rem]' : 'max-w-[28rem]'
77+
'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',
78+
widthClass[width]
7179
)}
7280
>
7381
<Dialog.Title className="border-b px-4 py-4 text-sans-semi-lg bg-secondary border-b-secondary">

0 commit comments

Comments
 (0)