Skip to content

Commit 3a6f815

Browse files
authored
instance create: focus image field on validation error (#2364)
* instance create: focus image field on validation error * don't fix the type issue, but fix the diff * the ref doesn't need to be a ref ref. it can be a regular prop * Listbox -> ListboxField in floating IP attach modal (noticed while checking existing Listbox references)
1 parent 80b3f2f commit 3a6f815

File tree

3 files changed

+13
-10
lines changed

3 files changed

+13
-10
lines changed

app/components/form/fields/ListboxField.tsx

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -79,6 +79,7 @@ export function ListboxField<
7979
name={name}
8080
hasError={fieldState.error !== undefined}
8181
isLoading={isLoading}
82+
buttonRef={field.ref}
8283
/>
8384
<ErrorMessage error={fieldState.error} label={label} />
8485
</div>

app/pages/project/floating-ips/FloatingIpsPage.tsx

Lines changed: 7 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,7 @@ import {
2222
import { IpGlobal16Icon, IpGlobal24Icon } from '@oxide/design-system/icons/react'
2323

2424
import { DocsPopover } from '~/components/DocsPopover'
25+
import { ListboxField } from '~/components/form/fields/ListboxField'
2526
import { HL } from '~/components/HL'
2627
import { getProjectSelector, useProjectSelector } from '~/hooks'
2728
import { confirmAction } from '~/stores/confirm-action'
@@ -34,7 +35,6 @@ import { Columns } from '~/table/columns/common'
3435
import { PAGE_SIZE, useQueryTable } from '~/table/QueryTable'
3536
import { CreateLink } from '~/ui/lib/CreateButton'
3637
import { EmptyMessage } from '~/ui/lib/EmptyMessage'
37-
import { Listbox } from '~/ui/lib/Listbox'
3838
import { Message } from '~/ui/lib/Message'
3939
import { Modal } from '~/ui/lib/Modal'
4040
import { PageHeader, PageTitle } from '~/ui/lib/PageHeader'
@@ -268,6 +268,7 @@ const AttachFloatingIpModal = ({
268268
},
269269
})
270270
const form = useForm({ defaultValues: { instanceId: '' } })
271+
const instanceId = form.watch('instanceId')
271272

272273
return (
273274
<Modal isOpen title="Attach floating IP" onDismiss={onDismiss}>
@@ -280,30 +281,27 @@ const AttachFloatingIpModal = ({
280281
The selected instance will be reachable at <HL>{address}</HL>
281282
</>
282283
}
283-
></Message>
284+
/>
284285
<form>
285-
<Listbox
286+
<ListboxField
287+
control={form.control}
286288
name="instanceId"
287289
items={instances.map((i) => ({ value: i.id, label: i.name }))}
288290
label="Instance"
289-
onChange={(e) => {
290-
form.setValue('instanceId', e)
291-
}}
292291
required
293292
placeholder="Select an instance"
294-
selected={form.watch('instanceId')}
295293
/>
296294
</form>
297295
</Modal.Section>
298296
</Modal.Body>
299297
<Modal.Footer
300298
actionText="Attach"
301-
disabled={!form.getValues('instanceId')}
299+
disabled={!instanceId}
302300
onAction={() =>
303301
floatingIpAttach.mutate({
304302
path: { floatingIp },
305303
query: { project },
306-
body: { kind: 'instance', parent: form.getValues('instanceId') },
304+
body: { kind: 'instance', parent: instanceId },
307305
})
308306
}
309307
onDismiss={onDismiss}

app/ui/lib/Listbox.tsx

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,7 @@ import {
1313
ListboxOptions,
1414
} from '@headlessui/react'
1515
import cn from 'classnames'
16-
import type { ReactNode } from 'react'
16+
import { type ReactNode, type Ref } from 'react'
1717

1818
import { SelectArrows6Icon } from '@oxide/design-system/icons/react'
1919

@@ -42,6 +42,8 @@ export interface ListboxProps<Value extends string = string> {
4242
description?: React.ReactNode
4343
required?: boolean
4444
isLoading?: boolean
45+
/** Necessary if you want RHF to be able to focus it on error */
46+
buttonRef?: Ref<HTMLButtonElement>
4547
}
4648

4749
export const Listbox = <Value extends string = string>({
@@ -59,6 +61,7 @@ export const Listbox = <Value extends string = string>({
5961
required,
6062
disabled,
6163
isLoading = false,
64+
buttonRef,
6265
...props
6366
}: ListboxProps<Value>) => {
6467
const selectedItem = selected && items.find((i) => i.value === selected)
@@ -100,6 +103,7 @@ export const Listbox = <Value extends string = string>({
100103
: 'bg-default',
101104
isDisabled && hasError && '!border-error-secondary'
102105
)}
106+
ref={buttonRef}
103107
{...props}
104108
>
105109
<div className="w-full overflow-hidden overflow-ellipsis whitespace-pre px-3 text-left">

0 commit comments

Comments
 (0)