Skip to content
40 changes: 18 additions & 22 deletions app/components/StatusBadge.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -3,16 +3,16 @@ import type { BadgeColor, BadgeProps } from '@oxide/ui'
import { Badge } from '@oxide/ui'

const INSTANCE_COLORS: Record<InstanceState, Pick<BadgeProps, 'color' | 'variant'>> = {
creating: { color: 'notice', variant: 'default' },
starting: { color: 'notice' },
creating: { color: 'purple', variant: 'solid' },
starting: { color: 'blue', variant: 'solid' },
running: { color: 'default' },
rebooting: { color: 'notice' },
stopping: { color: 'notice' },
stopped: { color: 'neutral', variant: 'default' },
repairing: { color: 'notice' },
migrating: { color: 'notice' },
failed: { color: 'destructive' },
destroyed: { color: 'neutral' },
stopped: { color: 'neutral', variant: 'solid' },
repairing: { color: 'notice', variant: 'solid' },
migrating: { color: 'notice', variant: 'solid' },
failed: { color: 'destructive', variant: 'solid' },
destroyed: { color: 'neutral', variant: 'solid' },
}

export const InstanceStatusBadge = (props: {
Expand All @@ -26,19 +26,19 @@ export const InstanceStatusBadge = (props: {

type DiskStateStr = DiskState['state']

const DISK_COLORS: Record<DiskStateStr, BadgeColor> = {
attached: 'default',
attaching: 'notice',
creating: 'notice',
detaching: 'notice',
detached: 'neutral',
destroyed: 'neutral', // should we ever see this?
faulted: 'destructive',
maintenance: 'notice',
const DISK_COLORS: Record<DiskStateStr, Pick<BadgeProps, 'color' | 'variant'>> = {
attached: { color: 'default' },
attaching: { color: 'blue', variant: 'solid' },
creating: { color: 'purple', variant: 'solid' },
detaching: { color: 'notice', variant: 'solid' },
detached: { color: 'neutral', variant: 'solid' },
destroyed: { color: 'destructive', variant: 'solid' }, // should we ever see this?
faulted: { color: 'destructive', variant: 'solid' },
maintenance: { color: 'notice', variant: 'solid' },
}

export const DiskStatusBadge = (props: { status: DiskStateStr; className?: string }) => (
<Badge color={DISK_COLORS[props.status]} className={props.className}>
<Badge {...DISK_COLORS[props.status]} className={props.className}>
{props.status}
</Badge>
)
Expand All @@ -54,11 +54,7 @@ export const SnapshotStatusBadge = (props: {
status: SnapshotState
className?: string
}) => (
<Badge
variant="default"
color={SNAPSHOT_COLORS[props.status]}
className={props.className}
>
<Badge color={SNAPSHOT_COLORS[props.status]} className={props.className}>
{props.status}
</Badge>
)
18 changes: 16 additions & 2 deletions app/components/form/fields/DisksTableField.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,8 @@ import type { Control } from 'react-hook-form'
import { useController } from 'react-hook-form'

import type { DiskCreate, DiskIdentifier } from '@oxide/api'
import { Button, Error16Icon, FieldLabel, MiniTable } from '@oxide/ui'
import { Badge, Button, Error16Icon, FieldLabel, MiniTable } from '@oxide/ui'
import { bytesToGiB } from '@oxide/util'

import AttachDiskSideModalForm from 'app/forms/disk-attach'
import { CreateDiskSideModalForm } from 'app/forms/disk-create'
Expand Down Expand Up @@ -34,6 +35,7 @@ export function DisksTableField({ control }: { control: Control<InstanceCreateIn
<MiniTable.Header>
<MiniTable.HeadCell>Name</MiniTable.HeadCell>
<MiniTable.HeadCell>Type</MiniTable.HeadCell>
<MiniTable.HeadCell>Size</MiniTable.HeadCell>
{/* For remove button */}
<MiniTable.HeadCell className="w-12" />
</MiniTable.Header>
Expand All @@ -46,7 +48,19 @@ export function DisksTableField({ control }: { control: Control<InstanceCreateIn
key={item.name}
>
<MiniTable.Cell>{item.name}</MiniTable.Cell>
<MiniTable.Cell>{item.type}</MiniTable.Cell>
<MiniTable.Cell>
<Badge variant="solid">{item.type}</Badge>
</MiniTable.Cell>
<MiniTable.Cell>
{item.type === 'attach' ? (
'-'
) : (
<>
<span>{bytesToGiB(item.size)}</span>
<span className="ml-1 inline-block text-accent-secondary">GiB</span>
</>
)}
</MiniTable.Cell>
Copy link
Collaborator

@david-crespo david-crespo Mar 1, 2023

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I like the size column, don't really like Source: New, though I admit Type: Attach/Create is definitely weird. How about Type: Existing/New? There's not a good word for this. Maybe there's some more subtle way of indicating existing vs. new, like a column giving some other attribute of the disk that indicates concretely that it exists. Like a created at column showing the create time but obviously only the existing disks have one.

image

I still find the green active styling with nested outlines kind of busy.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think language consistency here is important. When working with disks across different product interfaces the InstanceDiskAttachment language of attach/create should be a consistent touch point. We don't want to use different language here than we would in the docs, API, CLI, etc. I would keep the Type: Attach/Create nomenclature but potentially add a tooltip on the header if further clarification were needed.

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yeah, I do find that convincing. When in doubt, stick close to the API and don't dress it up.

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I changed it back to create/attach but I put it in a badge because it looked weird:

Before

Screenshot 2023-03-02 at 3 09 12 PM

After

Screenshot 2023-03-02 at 3 08 49 PM

<MiniTable.Cell>
<button
onClick={() => onChange(items.filter((i) => i.name !== item.name))}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -203,7 +203,7 @@ export function NetworkingTab() {
A network interface cannot be created or edited without{' '}
<a href="#/" className="text-accent-secondary">
stopping the instance
<OpenLink12Icon className="ml-1 pt-[1px]" />
<OpenLink12Icon className="ml-1 align-middle" />
</a>
</span>
)}
Expand Down
2 changes: 1 addition & 1 deletion app/pages/project/instances/instance/tabs/StorageTab.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -179,7 +179,7 @@ export function StorageTab() {
A disk cannot be added or attached without first{' '}
<a href="#/" className="text-accent-secondary">
stopping the instance
<OpenLink12Icon className="ml-1 pt-[1px]" />
<OpenLink12Icon className="ml-1 align-middle" />
</a>
</span>
)}
Expand Down
87 changes: 44 additions & 43 deletions libs/table/columns/action-col.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -20,57 +20,58 @@ export const getActionsCol = <TData extends { id?: string }>(
return {
id: 'menu',
header: '',
meta: { thClassName: 'w-12 action-col', tdClassName: 'action-col' },
meta: {
thClassName: 'action-col',
tdClassName: 'action-col children:p-0 w-10',
},

cell: ({ row }) => {
// TODO: control flow here has always confused me, would like to straighten it out
const actions = makeActions(row.original)
const id = row.original.id
return (
<div className="flex w-full justify-center">
<Menu>
{/* TODO: This name should not suck; future us, make it so! */}
{/* stopPropagation prevents clicks from toggling row select in a single select table */}
<MenuButton
className="-m-3 p-3"
aria-label="Row actions"
onClick={(e) => e.stopPropagation()}
>
<More12Icon className="text-tertiary" />
</MenuButton>
<MenuList>
{id && (
<MenuItem
onSelect={() => {
window.navigator.clipboard.writeText(id)
}}
<Menu>
{/* TODO: This name should not suck; future us, make it so! */}
{/* stopPropagation prevents clicks from toggling row select in a single select table */}
<MenuButton
className="flex h-full w-10 items-center justify-center"
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is to make the whole cell clickable, right? Nice.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yep, was previously padded out with a large clickable area but it wasn't edge-to-edge.

aria-label="Row actions"
onClick={(e) => e.stopPropagation()}
>
<More12Icon className="text-tertiary" />
</MenuButton>
<MenuList>
{id && (
<MenuItem
onSelect={() => {
window.navigator.clipboard.writeText(id)
}}
>
Copy ID
</MenuItem>
)}
{actions.map((action) => {
return (
<Wrap
when={!!action.disabled}
with={<Tooltip content={action.disabled} />}
key={kebabCase(`action-${action.label}`)}
>
Copy ID
</MenuItem>
)}
{actions.map((action) => {
return (
<Wrap
when={!!action.disabled}
with={<Tooltip content={action.disabled} />}
key={kebabCase(`action-${action.label}`)}
<MenuItem
className={cn(action.className, {
destructive:
action.label.toLowerCase() === 'delete' && !action.disabled,
})}
onSelect={action.onActivate}
disabled={!!action.disabled}
>
<MenuItem
className={cn(action.className, {
destructive:
action.label.toLowerCase() === 'delete' && !action.disabled,
})}
onSelect={action.onActivate}
disabled={!!action.disabled}
>
{action.label}
</MenuItem>
</Wrap>
)
})}
</MenuList>
</Menu>
</div>
{action.label}
</MenuItem>
</Wrap>
)
})}
</MenuList>
</Menu>
)
},
}
Expand Down
41 changes: 20 additions & 21 deletions libs/ui/lib/badge/Badge.tsx
Original file line number Diff line number Diff line change
@@ -1,8 +1,13 @@
import cn from 'classnames'
import invariant from 'tiny-invariant'

export type BadgeColor = 'default' | 'destructive' | 'notice' | 'neutral'
export type BadgeVariant = 'default' | 'secondary' | 'ghost'
export type BadgeColor =
| 'default'
| 'destructive'
| 'notice'
| 'neutral'
| 'purple'
| 'blue'
export type BadgeVariant = 'default' | 'solid'

export interface BadgeProps {
color?: BadgeColor
Expand All @@ -11,37 +16,31 @@ export interface BadgeProps {
variant?: BadgeVariant
}

export const badgeColors: Record<BadgeVariant, Partial<Record<BadgeColor, string>>> = {
export const badgeColors: Record<BadgeVariant, Record<BadgeColor, string>> = {
default: {
default: `ring-1 ring-inset bg-accent-secondary text-accent ring-[rgba(var(--base-green-800-rgb),0.15)]`,
destructive: `ring-1 ring-inset bg-destructive-secondary text-destructive ring-[rgba(var(--base-red-800-rgb),0.15)]`,
notice: `ring-1 ring-inset bg-notice-secondary text-notice ring-[rgba(var(--base-yellow-800-rgb),0.15)]`,
neutral: `ring-1 ring-inset bg-secondary text-secondary ring-[rgba(var(--base-neutral-700-rgb),0.15)]`,
purple: `ring-1 ring-inset bg-[var(--base-purple-200)] text-[var(--base-purple-700)] ring-[rgba(var(--base-purple-700-rgb),0.15)]`,
blue: `ring-1 ring-inset bg-[var(--base-blue-200)] text-[var(--base-blue-700)] ring-[rgba(var(--base-blue-700-rgb),0.15)]`,
},
solid: {
default: 'bg-accent text-inverse',
destructive: 'bg-destructive text-inverse',
notice: 'bg-notice text-inverse',
neutral: 'bg-inverse-tertiary text-inverse',
},
secondary: {
default: 'bg-accent-secondary text-accent',
destructive: 'bg-destructive-secondary text-destructive',
notice: 'bg-notice-secondary text-notice',
neutral: 'bg-secondary text-secondary',
},
ghost: {
default: 'ring-1 ring-inset bg-accent-secondary ring-accent-tertiary text-accent',
destructive:
'ring-1 ring-inset bg-destructive-secondary ring-destructive-tertiary text-destructive',
notice: 'ring-1 ring-inset bg-notice-secondary ring-notice-tertiary text-notice',
purple: 'bg-[var(--base-purple-700)] text-[var(--base-purple-200)]',
blue: 'bg-[var(--base-blue-700)] text-[var(--base-blue-200)]',
},
}

export const Badge = ({
className,
children,
color = 'default',
variant = 'secondary',
variant = 'default',
}: BadgeProps) => {
invariant(
badgeColors[variant][color],
`${variant} ${color} is not a valid variant/color combination`
)
return (
<span
className={cn(
Expand Down
90 changes: 55 additions & 35 deletions libs/ui/lib/mini-table/mini-table.css
Original file line number Diff line number Diff line change
@@ -1,47 +1,67 @@
.ox-mini-table {
border-spacing: 0px;
}
& {
border-spacing: 0px;
}

.ox-mini-table td {
@apply relative px-0 pt-2;
}
& td {
@apply relative px-0 pt-2;
}

.ox-mini-table tr {
@apply relative;
}
& tr {
@apply relative;
}

.ox-mini-table td + td:before {
@apply absolute top-[calc(0.5rem+1px)] bottom-[2px] block w-[1px] border-l border-secondary;
content: ' ';
}
& td + td:before {
@apply absolute top-[calc(0.5rem+1px)] bottom-[2px] block w-[1px] border-l border-secondary;
content: ' ';
}

.ox-mini-table tr:last-child td + td:before {
@apply bottom-[calc(0.5rem+2px)];
}
& tr:last-child td + td:before {
@apply bottom-[calc(0.5rem+2px)];
}

.ox-mini-table td > div {
@apply flex h-11 items-center border-y py-3 pl-3 text-accent bg-accent-secondary border-accent;
}
& td > div {
@apply flex h-11 items-center border-y py-3 pl-3 text-accent bg-accent-secondary border-accent-secondary;
}

.ox-mini-table td:last-child > div {
@apply w-12 justify-center pl-0 pr-0;
}
.ox-mini-table td:last-child > div > button {
@apply -mx-3 -my-3 flex items-center justify-center px-3 py-3;
}
& td:last-child > div {
@apply w-12 justify-center pl-0 pr-0;
}
& td:last-child > div > button {
@apply -mx-3 -my-3 flex items-center justify-center px-3 py-3;
}

.ox-mini-table tr:hover td > div {
@apply bg-accent-secondary-hover;
}
& tr:hover td > div {
@apply bg-accent-secondary-hover;
}

.ox-mini-table tr:last-child td {
@apply pb-2;
}
& tr:last-child td {
@apply pb-2;
}

.ox-mini-table td:first-child > div {
@apply ml-2 rounded-l border-l;
}
& td:first-child > div {
@apply ml-2 rounded-l border-l;
}

& td:last-child > div {
@apply mr-2 rounded-r border-r;
}

& thead tr:first-of-type th:first-of-type {
border-top-left-radius: var(--border-radius-lg);
@apply border-l;
}

& thead tr:first-of-type th:last-of-type {
border-top-right-radius: var(--border-radius-lg);
@apply border-r;
}

& tbody tr:last-of-type td:first-of-type {
border-bottom-left-radius: var(--border-radius-lg);
}

.ox-mini-table td:last-child > div {
@apply mr-2 rounded-r border-r;
& tbody tr:last-of-type td:last-of-type {
border-bottom-right-radius: var(--border-radius-lg);
}
}
Loading