diff --git a/app/components/form/fields/DisksTableField.tsx b/app/components/form/fields/DisksTableField.tsx index 6134b505f..6a78ce9f6 100644 --- a/app/components/form/fields/DisksTableField.tsx +++ b/app/components/form/fields/DisksTableField.tsx @@ -13,6 +13,7 @@ import type { DiskCreate } from '@oxide/api' import { AttachDiskModalForm } from '~/forms/disk-attach' import { CreateDiskSideModalForm } from '~/forms/disk-create' import type { InstanceCreateInput } from '~/forms/instance-create' +import { EmptyCell } from '~/table/cells/EmptyCell' import { Badge } from '~/ui/lib/Badge' import { Button } from '~/ui/lib/Button' import * as MiniTable from '~/ui/lib/MiniTable' @@ -45,18 +46,18 @@ export function DisksTableField({ return ( <> -
- {!!items.length && ( - - - Name - Type - Size - {/* For remove button */} - - - - {items.map((item, index) => ( +
+ + + Name + Type + Size + {/* For remove button */} + + + + {items.length ? ( + items.map((item, index) => ( - {item.type} + {item.type} {item.type === 'attach' ? ( - '—' + ) : ( <> {bytesToGiB(item.size)} - GiB + GiB )} @@ -84,17 +85,23 @@ export function DisksTableField({ label={`remove disk ${item.name}`} /> - ))} - - - )} + )) + ) : ( + + )} + +
)} - { ) } +export const EmptyState = (props: { title: string; body: string; colSpan: number }) => ( + + +
+ +
+ +
+) + +export const InputCell = ({ + colSpan, + defaultValue, + placeholder, +}: { + colSpan?: number + defaultValue: string + placeholder: string +}) => ( + +
+ +
+ +) + // followed this for icon in button best practices // https://www.sarasoueidan.com/blog/accessible-icon-buttons/ export const RemoveCell = ({ onClick, label }: { onClick: () => void; label: string }) => ( diff --git a/app/ui/styles/components/mini-table.css b/app/ui/styles/components/mini-table.css index 27f2098bb..5bbcc717b 100644 --- a/app/ui/styles/components/mini-table.css +++ b/app/ui/styles/components/mini-table.css @@ -11,16 +11,20 @@ border-spacing: 0px; } - & td { - @apply relative px-0 pt-2; - } - + /* all rows */ & tr { + @apply bg-default; @apply relative; } + /* all cells */ + & td { + @apply relative px-0 pt-2; + } + + /* a fake left border for all cells that aren't first */ & td + td:before { - @apply absolute bottom-[2px] top-[calc(0.5rem+1px)] block w-[1px] border-l opacity-40 border-accent-tertiary; + @apply absolute bottom-[2px] top-[calc(0.5rem+1px)] block w-[1px] border-l border-secondary; content: ' '; } @@ -28,30 +32,33 @@ @apply bottom-[calc(0.5rem+2px)]; } + /* all divs */ & td > div { - @apply flex h-11 items-center border-y py-3 pl-3 pr-6 text-accent bg-accent-secondary border-accent-tertiary; + @apply flex h-9 items-center border border-y border-r-0 py-3 pl-3 pr-6 border-default; } - & 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; + /* first cell's div */ + & td:first-child > div { + @apply ml-2 rounded-l border-l; } - & td:last-child > div:has(button:hover, button:focus) { - @apply bg-accent-secondary-hover; + + /* second-to-last cell's div */ + & td:nth-last-child(2) > div { + @apply rounded-r border-r; } - & tr:last-child td { - @apply pb-2; + /* last cell's div (the div for the delete button) */ + & td:last-child > div { + @apply flex w-8 items-center justify-center border-none px-5; } - & td:first-child > div { - @apply ml-2 rounded-l border-l; + /* the delete button */ + & td:last-child > div > button { + @apply -m-2 flex items-center justify-center p-2 text-tertiary hover:text-secondary focus:text-secondary; } - & td:last-child > div { - @apply mr-2 rounded-r border-r; + & tr:last-child td { + @apply pb-2; } & thead tr:first-of-type th:first-of-type { @@ -61,7 +68,7 @@ & thead tr:first-of-type th:last-of-type { border-top-right-radius: var(--border-radius-lg); - @apply border-r; + @apply w-8 border-r; } & tbody tr:last-of-type td:first-of-type { diff --git a/test/e2e/instance-create.e2e.ts b/test/e2e/instance-create.e2e.ts index c93ce63d4..9d9357b38 100644 --- a/test/e2e/instance-create.e2e.ts +++ b/test/e2e/instance-create.e2e.ts @@ -257,6 +257,7 @@ test('can’t create a disk with a name that collides with the boot disk name', await page.fill('input[name=bootDiskName]', 'disk-11') // Attempt to create a disk with the same name + await expect(page.getByText('No disks')).toBeVisible() await page.getByRole('button', { name: 'Create new disk' }).click() const dialog = page.getByRole('dialog') await dialog.getByRole('textbox', { name: 'name' }).fill('disk-11') @@ -268,6 +269,7 @@ test('can’t create a disk with a name that collides with the boot disk name', await dialog.getByRole('button', { name: 'Create disk' }).click() // The disk has been "created" (is in the list of Additional Disks) await expectVisible(page, ['text=disk-12']) + await expect(page.getByText('No disks')).toBeHidden() // Create the instance await page.getByRole('button', { name: 'Create instance' }).click() await expect(page).toHaveURL('/projects/mock-project/instances/another-instance/storage')