@@ -10,12 +10,12 @@ import { useController, type Control } from 'react-hook-form'
10
10
import type { Image } from '@oxide/api'
11
11
12
12
import type { InstanceCreateInput } from '~/forms/instance-create'
13
- import type { ListboxItem } from '~/ui/lib/Listbox '
13
+ import type { ComboboxItem } from '~/ui/lib/Combobox '
14
14
import { Slash } from '~/ui/lib/Slash'
15
15
import { nearest10 } from '~/util/math'
16
16
import { bytesToGiB , GiB } from '~/util/units'
17
17
18
- import { ListboxField } from './ListboxField '
18
+ import { ComboboxField } from './ComboboxField '
19
19
20
20
type ImageSelectFieldProps = {
21
21
images : Image [ ]
@@ -32,18 +32,22 @@ export function BootDiskImageSelectField({
32
32
} : ImageSelectFieldProps ) {
33
33
const diskSizeField = useController ( { control, name : 'bootDiskSize' } ) . field
34
34
return (
35
- // This should be migrated to a `ComboboxField` (and with a `toComboboxItem`), once
36
- // we have a combobox that supports more elaborate labels (beyond just strings).
37
- < ListboxField
35
+ < ComboboxField
38
36
disabled = { disabled }
39
37
control = { control }
40
38
name = { name }
41
39
label = "Image"
42
- placeholder = "Select an image"
43
- items = { images . map ( ( i ) => toListboxItem ( i ) ) }
40
+ placeholder = {
41
+ name === 'siloImageSource' ? 'Select a silo image' : 'Select a project image'
42
+ }
43
+ items = { images . map ( ( i ) => toImageComboboxItem ( i ) ) }
44
44
required
45
45
onChange = { ( id ) => {
46
- const image = images . find ( ( i ) => i . id === id ) ! // if it's selected, it must be present
46
+ const image = images . find ( ( i ) => i . id === id )
47
+ // the most likely scenario where image would be undefined is if the user has
48
+ // manually cleared the ComboboxField; they will need to pick a boot disk image
49
+ // in order to submit the form, so we don't need to do anything here
50
+ if ( ! image ) return
47
51
const imageSizeGiB = image . size / GiB
48
52
if ( diskSizeField . value < imageSizeGiB ) {
49
53
diskSizeField . onChange ( nearest10 ( imageSizeGiB ) )
@@ -53,24 +57,18 @@ export function BootDiskImageSelectField({
53
57
)
54
58
}
55
59
56
- export function toListboxItem ( i : Image , includeProjectSiloIndicator = false ) : ListboxItem {
57
- const { name, os, projectId, size, version } = i
58
- const formattedSize = `${ bytesToGiB ( size , 1 ) } GiB`
59
-
60
- // filter out any undefined metadata and create a comma-separated list
61
- // for the selected listbox item (shown in selectedLabel)
62
- const condensedImageMetadata = [ os , version , formattedSize ] . filter ( ( i ) => ! ! i ) . join ( ', ' )
63
- const metadataForSelectedLabel = condensedImageMetadata . length
64
- ? ` (${ condensedImageMetadata } )`
65
- : ''
60
+ export function toImageComboboxItem (
61
+ image : Image ,
62
+ includeProjectSiloIndicator = false
63
+ ) : ComboboxItem {
64
+ const { id, name, os, projectId, size, version } = image
66
65
67
66
// for metadata showing in the dropdown's options, include the project / silo indicator if requested
68
67
const projectSiloIndicator = includeProjectSiloIndicator
69
68
? `${ projectId ? 'Project' : 'Silo' } image`
70
69
: null
71
- // filter out undefined metadata here, as well, and create a `<Slash />`-separated list
72
- // for the listbox item (shown for each item in the dropdown)
73
- const metadataForLabel = [ os , version , formattedSize , projectSiloIndicator ]
70
+ // filter out undefined metadata and create a `<Slash />`-separated list for each comboboxitem
71
+ const itemMetadata = [ os , version , `${ bytesToGiB ( size , 1 ) } GiB` , projectSiloIndicator ]
74
72
. filter ( ( i ) => ! ! i )
75
73
. map ( ( i , index ) => (
76
74
< span key = { `${ i } ` } >
@@ -79,14 +77,12 @@ export function toListboxItem(i: Image, includeProjectSiloIndicator = false): Li
79
77
</ span >
80
78
) )
81
79
return {
82
- value : i . id ,
83
- selectedLabel : ` ${ name } ${ metadataForSelectedLabel } ` ,
80
+ value : id ,
81
+ selectedLabel : name ,
84
82
label : (
85
83
< >
86
84
< div > { name } </ div >
87
- < div className = "text-tertiary selected:text-accent-secondary" >
88
- { metadataForLabel }
89
- </ div >
85
+ < div className = "text-tertiary selected:text-accent-secondary" > { itemMetadata } </ div >
90
86
</ >
91
87
) ,
92
88
}
0 commit comments