From 8cf89ced7965f7142fa4a88ba0667b7064e385c4 Mon Sep 17 00:00:00 2001 From: Charlie Park Date: Thu, 2 Jul 2026 10:19:45 -0700 Subject: [PATCH] Refactor value + unit renderings to use unified layout --- .../form/fields/DisksTableField.tsx | 4 +-- app/pages/project/instances/InstancePage.tsx | 11 +++----- app/pages/project/instances/InstancesPage.tsx | 16 ++--------- app/pages/system/inventory/sled/SledPage.tsx | 11 +++----- app/table/cells/InstanceResourceCell.tsx | 23 +++++++-------- app/table/columns/common.tsx | 14 ++-------- app/ui/lib/PropertiesTable.tsx | 8 ++++-- app/ui/lib/ValueUnit.tsx | 28 +++++++++++++++++++ 8 files changed, 59 insertions(+), 56 deletions(-) create mode 100644 app/ui/lib/ValueUnit.tsx diff --git a/app/components/form/fields/DisksTableField.tsx b/app/components/form/fields/DisksTableField.tsx index 2f56e61aef..996b7c445e 100644 --- a/app/components/form/fields/DisksTableField.tsx +++ b/app/components/form/fields/DisksTableField.tsx @@ -14,10 +14,10 @@ import { Badge } from '@oxide/design-system/ui' import { AttachDiskModalForm } from '~/forms/disk-attach' import { CreateDiskSideModalForm } from '~/forms/disk-create' import type { InstanceCreateInput } from '~/forms/instance-create' -import { sizeCellInner } from '~/table/columns/common' import { Button } from '~/ui/lib/Button' import { MiniTable } from '~/ui/lib/MiniTable' import { Truncate } from '~/ui/lib/Truncate' +import { Size } from '~/ui/lib/ValueUnit' export type DiskTableItem = | (DiskCreate & { action: 'create' }) @@ -68,7 +68,7 @@ export function DisksTableField({ }, { header: 'Size', - cell: (item) => sizeCellInner(item.size), + cell: (item) => , }, ]} rowKey={(item) => item.name} diff --git a/app/pages/project/instances/InstancePage.tsx b/app/pages/project/instances/InstancePage.tsx index c8146cc865..0fb7c74609 100644 --- a/app/pages/project/instances/InstancePage.tsx +++ b/app/pages/project/instances/InstancePage.tsx @@ -52,10 +52,11 @@ import { Modal } from '~/ui/lib/Modal' import { PageHeader, PageTitle } from '~/ui/lib/PageHeader' import { PropertiesTable } from '~/ui/lib/PropertiesTable' import { truncate } from '~/ui/lib/Truncate' +import { Size, ValueUnit } from '~/ui/lib/ValueUnit' import { instanceMetricsBase, pb } from '~/util/path-builder' import type * as PP from '~/util/path-params' import { pluralize } from '~/util/str' -import { formatBytes, GiB } from '~/util/units' +import { GiB } from '~/util/units' import { useMakeInstanceActions } from './actions' @@ -167,8 +168,6 @@ export default function InstancePage() { enabled: !!primaryVpcId, }) - const memory = formatBytes(instance.memory) - return ( <> @@ -216,12 +215,10 @@ export default function InstancePage() { - {instance.ncpus} - {pluralize(' vCPU', instance.ncpus)} + - {memory.value} - {memory.unit} +
diff --git a/app/pages/project/instances/InstancesPage.tsx b/app/pages/project/instances/InstancesPage.tsx index 19af2687db..ae59c3dd2a 100644 --- a/app/pages/project/instances/InstancesPage.tsx +++ b/app/pages/project/instances/InstancesPage.tsx @@ -36,12 +36,12 @@ import { EmptyMessage } from '~/ui/lib/EmptyMessage' import { PageHeader, PageTitle } from '~/ui/lib/PageHeader' import { TableActions } from '~/ui/lib/Table' import { Tooltip } from '~/ui/lib/Tooltip' +import { Size, ValueUnit } from '~/ui/lib/ValueUnit' import { setDiff } from '~/util/array' import { ALL_ISH } from '~/util/consts' import { toLocaleTimeString } from '~/util/date' import { pb } from '~/util/path-builder' import { pluralize } from '~/util/str' -import { formatBytes } from '~/util/units' import { useMakeInstanceActions } from './actions' import { ResizeInstanceModal } from './InstancePage' @@ -104,22 +104,12 @@ export default function InstancesPage() { colHelper.accessor('ncpus', { header: 'CPU', cell: (info) => ( - <> - {info.getValue()}{' '} - {pluralize('vCPU', info.getValue())} - + ), }), colHelper.accessor('memory', { header: 'Memory', - cell: (info) => { - const memory = formatBytes(info.getValue()) - return ( - <> - {memory.value} {memory.unit} - - ) - }, + cell: (info) => , }), colHelper.accessor( (i) => ({ runState: i.runState, timeRunStateUpdated: i.timeRunStateUpdated }), diff --git a/app/pages/system/inventory/sled/SledPage.tsx b/app/pages/system/inventory/sled/SledPage.tsx index 7947e6c661..1a64da86c7 100644 --- a/app/pages/system/inventory/sled/SledPage.tsx +++ b/app/pages/system/inventory/sled/SledPage.tsx @@ -18,7 +18,6 @@ import { PropertiesTable } from '~/ui/lib/PropertiesTable' import { truncate } from '~/ui/lib/Truncate' import { pb } from '~/util/path-builder' import type * as PP from '~/util/path-params' -import { formatBytes } from '~/util/units' import { ProvisionPolicyBadge, SledKindBadge, SledStateBadge } from './SledBadges' @@ -38,8 +37,6 @@ export default function SledPage() { const { sledId } = useSledParams() const { data: sled } = usePrefetchedQuery(sledView({ sledId })) - const ram = formatBytes(sled.usablePhysicalRam) - return ( <> @@ -74,10 +71,10 @@ export default function SledPage() { {sled.rackId} - - {ram.value} - {ram.unit} - + diff --git a/app/table/cells/InstanceResourceCell.tsx b/app/table/cells/InstanceResourceCell.tsx index a735f54e14..4c4b1e985d 100644 --- a/app/table/cells/InstanceResourceCell.tsx +++ b/app/table/cells/InstanceResourceCell.tsx @@ -7,20 +7,17 @@ */ import type { Instance } from '@oxide/api' -import { formatBytes } from '~/util/units' +import { Size, ValueUnit } from '~/ui/lib/ValueUnit' type Props = { value: Pick } -export const InstanceResourceCell = ({ value }: Props) => { - const memory = formatBytes(value.memory) - return ( -
-
- {value.ncpus} vCPU -
-
- {memory.value} {memory.unit} -
+export const InstanceResourceCell = ({ value }: Props) => ( +
+
+
- ) -} +
+ +
+
+) diff --git a/app/table/columns/common.tsx b/app/table/columns/common.tsx index 99f0aa8d57..be46789857 100644 --- a/app/table/columns/common.tsx +++ b/app/table/columns/common.tsx @@ -11,7 +11,7 @@ import { InstanceStateBadge } from '~/components/StateBadge' import { DescriptionCell } from '~/table/cells/DescriptionCell' import { CopyToClipboard } from '~/ui/lib/CopyToClipboard' import { DateTime } from '~/ui/lib/DateTime' -import { formatBytes } from '~/util/units' +import { Size } from '~/ui/lib/ValueUnit' // the full type of the info arg is CellContext from RT, but in these // cells we only care about the return value of getValue @@ -37,16 +37,6 @@ function instanceStateCell(info: Info) { return } -// not using Info so this can also be used for minitables -export function sizeCellInner(value: number) { - const size = formatBytes(value) - return ( - - {size.value} {size.unit} - - ) -} - /** Columns used in a bunch of tables */ export const Columns = { /** Truncates text if too long, full text in tooltip */ @@ -55,7 +45,7 @@ export const Columns = { }, id: { header: 'ID', cell: idCell }, instanceState: { header: 'state', cell: instanceStateCell }, - size: { cell: (info: Info) => sizeCellInner(info.getValue()) }, + size: { cell: (info: Info) => }, timeCreated: { header: 'created', cell: dateCell }, timeModified: { header: 'modified', cell: dateCell }, } diff --git a/app/ui/lib/PropertiesTable.tsx b/app/ui/lib/PropertiesTable.tsx index 3322900d34..9f7f963036 100644 --- a/app/ui/lib/PropertiesTable.tsx +++ b/app/ui/lib/PropertiesTable.tsx @@ -10,13 +10,13 @@ import type { ReactNode } from 'react' import { DescriptionCell } from '~/table/cells/DescriptionCell' import { EmptyCell } from '~/table/cells/EmptyCell' -import { sizeCellInner } from '~/table/columns/common' import { isOneOf } from '~/util/children' import { invariant } from '~/util/invariant' import { CopyToClipboard } from './CopyToClipboard' import { DateTime } from './DateTime' import { Truncate } from './Truncate' +import { Size } from './ValueUnit' export interface PropertiesTableProps { className?: string @@ -110,7 +110,11 @@ PropertiesTable.SizeRow = ({ }: { bytes: number label?: string -}) => {sizeCellInner(bytes)} +}) => ( + + + +) PropertiesTable.CopyableRow = ({ label, text }: { label: string; text: string }) => ( diff --git a/app/ui/lib/ValueUnit.tsx b/app/ui/lib/ValueUnit.tsx new file mode 100644 index 0000000000..05bcb3f841 --- /dev/null +++ b/app/ui/lib/ValueUnit.tsx @@ -0,0 +1,28 @@ +/* + * 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 { formatBytes } from '~/util/units' + +/** + * A value with a dimmed unit label, e.g. `4 vCPU` or `16 GiB`: the value renders + * in the default text color, the unit in tertiary, separated by a single space. + */ +export function ValueUnit({ value, unit }: { value: ReactNode; unit: ReactNode }) { + return ( + + {value} {unit} + + ) +} + +/** Format a byte count with `formatBytes` and render it as a {@link ValueUnit}. */ +export function Size({ bytes }: { bytes: number }) { + const { value, unit } = formatBytes(bytes) + return +}