Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
26 changes: 11 additions & 15 deletions app/components/AttachFloatingIpModal.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@ import {
import { ListboxField } from '~/components/form/fields/ListboxField'
import { HL } from '~/components/HL'
import { addToast } from '~/stores/toast'
import { ItemLabel } from '~/ui/lib/ItemLabel'
import { Message } from '~/ui/lib/Message'
import { Slash } from '~/ui/lib/Slash'

Expand Down Expand Up @@ -50,21 +51,16 @@ function IpPoolName({ ipPoolId }: { ipPoolId: string }) {

function FloatingIpLabel({ fip }: { fip: FloatingIp }) {
return (
<div className="text-secondary selected:text-accent-secondary">
<div>{fip.name}</div>
<div className="flex gap-0.5">
<div>{fip.ip}</div>
<IpPoolName ipPoolId={fip.ipPoolId} />
{fip.description && (
<>
<Slash />
<div className="grow overflow-hidden text-left text-ellipsis whitespace-pre">
{fip.description}
</div>
</>
)}
</div>
</div>
<ItemLabel name={fip.name}>
{fip.ip}
<IpPoolName ipPoolId={fip.ipPoolId} />
{fip.description && (
<>
<Slash />
{fip.description}
</>
)}
</ItemLabel>
)
}

Expand Down
22 changes: 12 additions & 10 deletions app/components/PoolListboxItem.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ import type { IpVersion } from '@oxide/api'
import { Badge } from '@oxide/design-system/ui'

import { IpVersionBadge } from '~/components/IpVersionBadge'
import { ItemLabel } from '~/ui/lib/ItemLabel'
import type { ListboxItem } from '~/ui/lib/Listbox'

/** Common fields of SiloIpPool and SiloSubnetPool used for display */
Expand All @@ -25,16 +26,17 @@ export function toPoolItem(p: PoolLike): ListboxItem {
const value = p.name
const selectedLabel = p.name
const label = (
<div className="flex flex-col gap-1">
<div className="flex items-center gap-1.5">
{p.name}
{p.isDefault && <Badge color="neutral">default</Badge>}
<IpVersionBadge ipVersion={p.ipVersion} />
</div>
{!!p.description && (
<div className="text-secondary selected:text-accent-secondary">{p.description}</div>
)}
</div>
<ItemLabel
name={
<>
{p.name}
{p.isDefault && <Badge color="neutral">default</Badge>}
<IpVersionBadge ipVersion={p.ipVersion} />
</>
}
>
{p.description}
</ItemLabel>
)
return { value, selectedLabel, label }
}
8 changes: 2 additions & 6 deletions app/components/form/fields/ImageSelectField.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ import type { Image } from '@oxide/api'

import type { InstanceCreateInput } from '~/forms/instance-create'
import type { ComboboxItem } from '~/ui/lib/Combobox'
import { ItemLabel } from '~/ui/lib/ItemLabel'
import { Slash } from '~/ui/lib/Slash'
import { diskSizeNearest10 } from '~/util/math'
import { bytesToGiB, GiB } from '~/util/units'
Expand Down Expand Up @@ -79,11 +80,6 @@ export function toImageComboboxItem(
return {
value: id,
selectedLabel: name,
label: (
<div className="flex flex-col gap-1">
<div>{name}</div>
<div className="text-secondary selected:text-accent-secondary">{itemMetadata}</div>
</div>
),
label: <ItemLabel name={name}>{itemMetadata}</ItemLabel>,
}
}
14 changes: 6 additions & 8 deletions app/forms/disk-create.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,7 @@ import { HL } from '~/components/HL'
import { useProjectSelector } from '~/hooks/use-params'
import { addToast } from '~/stores/toast'
import { FieldLabel } from '~/ui/lib/FieldLabel'
import { ItemLabel } from '~/ui/lib/ItemLabel'
import { SideModalFormDocs } from '~/ui/lib/ModalLinks'
import { Radio } from '~/ui/lib/Radio'
import { RadioGroup } from '~/ui/lib/RadioGroup'
Expand Down Expand Up @@ -420,14 +421,11 @@ const SnapshotSelectField = ({ control }: { control: Control<DiskCreateForm> })
value: i.id,
selectedLabel: i.name,
label: (
<>
<div>{i.name}</div>
<div className="text-secondary selected:text-accent-secondary">
Created on {toLocaleDateString(i.timeCreated)}
<DiskNameFromId disk={i.diskId} /> <Slash /> {formattedSize.value}{' '}
{formattedSize.unit}
</div>
</>
<ItemLabel name={i.name}>
Created on {toLocaleDateString(i.timeCreated)}
<DiskNameFromId disk={i.diskId} /> <Slash /> {formattedSize.value}{' '}
{formattedSize.unit}
</ItemLabel>
),
}
})}
Expand Down
24 changes: 10 additions & 14 deletions app/forms/instance-create.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -70,6 +70,7 @@ import { Button } from '~/ui/lib/Button'
import { toComboboxItems } from '~/ui/lib/Combobox'
import { FormDivider } from '~/ui/lib/Divider'
import { EmptyMessage } from '~/ui/lib/EmptyMessage'
import { ItemLabel } from '~/ui/lib/ItemLabel'
import { Listbox } from '~/ui/lib/Listbox'
import { Message } from '~/ui/lib/Message'
import { MiniTable } from '~/ui/lib/MiniTable'
Expand Down Expand Up @@ -862,20 +863,15 @@ export default function CreateInstanceForm() {
}

const FloatingIpLabel = ({ ip }: { ip: FloatingIp }) => (
<div>
<div>{ip.name}</div>
<div className="text-secondary selected:text-accent-secondary flex gap-0.5">
<div>{ip.ip}</div>
{ip.description && (
<>
<Slash />
<div className="grow overflow-hidden text-left text-ellipsis whitespace-pre">
{ip.description}
</div>
</>
)}
</div>
</div>
<ItemLabel name={ip.name}>
{ip.ip}
{ip.description && (
<>
<Slash />
{ip.description}
</>
)}
</ItemLabel>
)

const NetworkingSection = ({
Expand Down
12 changes: 2 additions & 10 deletions app/pages/system/networking/IpPoolPage.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -53,6 +53,7 @@ import { type ComboboxItem } from '~/ui/lib/Combobox'
import { CreateButton, CreateLink } from '~/ui/lib/CreateButton'
import * as Dropdown from '~/ui/lib/DropdownMenu'
import { EmptyMessage } from '~/ui/lib/EmptyMessage'
import { ItemLabel } from '~/ui/lib/ItemLabel'
import { Message } from '~/ui/lib/Message'
import { Modal } from '~/ui/lib/Modal'
import { PageHeader, PageTitle } from '~/ui/lib/PageHeader'
Expand Down Expand Up @@ -476,16 +477,7 @@ const defaultValues: LinkSiloFormValues = { silo: undefined, isDefault: false }
const toSiloComboboxItem = ({ name, description }: Silo): ComboboxItem => ({
value: name,
selectedLabel: name,
label: (
<div className="flex flex-col gap-1">
<div>{name}</div>
{description && (
<div className="text-secondary selected:text-accent-secondary line-clamp-2 break-words">
{description}
</div>
)}
</div>
),
label: <ItemLabel name={name}>{description}</ItemLabel>,
})

function LinkSiloModal({ onDismiss }: { onDismiss: () => void }) {
Expand Down
40 changes: 40 additions & 0 deletions app/ui/lib/ItemLabel.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
/*
* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, you can obtain one at https://mozilla.org/MPL/2.0/.
*
* Copyright Oxide Computer Company
*/

import type { ReactNode } from 'react'

import { classed } from '~/util/classed'

/**
* Secondary line in a combobox or listbox item label, e.g., a description or
* metadata shown under the item name. The `selected:` variant keeps the text
* legible against the accent background of the highlighted option. User-supplied
* descriptions can be arbitrarily long, so clamp at two lines to keep option
* heights bounded, and break long unbroken tokens (like URLs) so they wrap
* instead of overflowing. Content must be inline (no flex/block children) or
* the line clamp won't apply.
*/
const ItemDescription = classed.div`text-secondary selected:text-accent-secondary line-clamp-2 break-words`

type ItemLabelProps = {
/** Resource name, plus any badges (the name row is a flex row with gap) */
name: ReactNode
/** Secondary line: a description, slash-separated metadata, or both */
children?: ReactNode
}

/**
* Standard two-line label for combobox and listbox items: resource name over
* an optional secondary line.
*/
export const ItemLabel = ({ name, children }: ItemLabelProps) => (
<div className="flex flex-col gap-1">
<div className="flex items-center gap-1.5">{name}</div>
{children && <ItemDescription>{children}</ItemDescription>}
</div>
)
Loading