diff --git a/app/components/MoreActionsMenu.tsx b/app/components/MoreActionsMenu.tsx index 67a6955dd..43a8ebced 100644 --- a/app/components/MoreActionsMenu.tsx +++ b/app/components/MoreActionsMenu.tsx @@ -8,7 +8,7 @@ import { More12Icon } from '@oxide/design-system/icons/react' import type { MenuAction } from '~/table/columns/action-col' -import { DropdownMenu } from '~/ui/lib/DropdownMenu' +import * as DropdownMenu from '~/ui/lib/DropdownMenu' import { Tooltip } from '~/ui/lib/Tooltip' import { Wrap } from '~/ui/util/wrap' @@ -26,7 +26,7 @@ export const MoreActionsMenu = ({ actions, label }: MoreActionsMenuProps) => { > - + {actions.map((a) => ( }>
{otherPickers}
- {/* */} - - + + + + {me.displayName || 'User'} + + - + Settings logout.mutate({})}> Sign out diff --git a/app/components/TopBarPicker.tsx b/app/components/TopBarPicker.tsx index 884335fc8..9e3c51599 100644 --- a/app/components/TopBarPicker.tsx +++ b/app/components/TopBarPicker.tsx @@ -24,8 +24,8 @@ import { } from '~/hooks/use-params' import { useCurrentUser } from '~/layouts/AuthenticatedLayout' import { PAGE_SIZE } from '~/table/QueryTable' -import { Button } from '~/ui/lib/Button' -import { DropdownMenu } from '~/ui/lib/DropdownMenu' +import { buttonStyle } from '~/ui/lib/Button' +import * as DropdownMenu from '~/ui/lib/DropdownMenu' import { Identicon } from '~/ui/lib/Identicon' import { Wrap } from '~/ui/util/wrap' import { pb } from '~/util/path-builder' @@ -95,14 +95,14 @@ const TopBarPicker = (props: TopBarPickerProps) => { {props.items && (
- + {/* aria-hidden is a tip from the Reach docs */} +
)} @@ -110,38 +110,36 @@ const TopBarPicker = (props: TopBarPickerProps) => { {/* TODO: item size and focus highlight */} {/* TODO: popover position should be further right */} {props.items && ( - // portal is necessary to avoid the menu popover getting its own after: - // separator thing - - - {props.items.length > 0 ? ( - props.items.map(({ label, to }) => { - const isSelected = props.current === label - return ( - - - - {label} - {isSelected && } - - - - ) - }) - ) : ( - {}} - disabled - > - {props.noItemsText || 'No items found'} - - )} - - + + {props.items.length > 0 ? ( + props.items.map(({ label, to }) => { + const isSelected = props.current === label + return ( + + + {label} + {isSelected && } + + + ) + }) + ) : ( + {}} + disabled + > + {props.noItemsText || 'No items found'} + + )} + )}
) diff --git a/app/table/columns/action-col.tsx b/app/table/columns/action-col.tsx index 58a348cf0..f18d16a04 100644 --- a/app/table/columns/action-col.tsx +++ b/app/table/columns/action-col.tsx @@ -11,7 +11,7 @@ import { useMemo } from 'react' import { More12Icon } from '@oxide/design-system/icons/react' -import { DropdownMenu } from '~/ui/lib/DropdownMenu' +import * as DropdownMenu from '~/ui/lib/DropdownMenu' import { Tooltip } from '~/ui/lib/Tooltip' import { Wrap } from '~/ui/util/wrap' import { kebabCase } from '~/util/str' @@ -75,41 +75,38 @@ export const RowActions = ({ id, copyIdLabel = 'Copy ID', actions }: RowActionsP > - {/* portal fixes mysterious z-index issue where menu is behind button */} - - - {id && ( - { - window.navigator.clipboard.writeText(id) - }} + {/* offset moves menu in from the right so it doesn't align with the table border */} + + {id && ( + { + window.navigator.clipboard.writeText(id) + }} + > + {copyIdLabel} + + )} + {actions?.map((action) => { + // TODO: Tooltip on disabled button broke, probably due to portal + return ( + } + key={kebabCase(`action-${action.label}`)} > - {copyIdLabel} - - )} - {actions?.map((action) => { - // TODO: Tooltip on disabled button broke, probably due to portal - return ( - } - key={kebabCase(`action-${action.label}`)} + - - {action.label} - - - ) - })} - - + {action.label} + + + ) + })} + ) } diff --git a/app/ui/lib/DropdownMenu.tsx b/app/ui/lib/DropdownMenu.tsx index 929296262..804e448bc 100644 --- a/app/ui/lib/DropdownMenu.tsx +++ b/app/ui/lib/DropdownMenu.tsx @@ -5,47 +5,77 @@ * * Copyright Oxide Computer Company */ + import { - Content, - Item, - Portal, - Root, - Trigger, - type DropdownMenuContentProps, - type DropdownMenuItemProps, -} from '@radix-ui/react-dropdown-menu' + Menu, + MenuButton, + MenuItem, + MenuItems, + type MenuItemsProps, +} from '@headlessui/react' import cn from 'classnames' -import { forwardRef, type ForwardedRef } from 'react' +import { forwardRef, type ForwardedRef, type ReactNode } from 'react' import { Link } from 'react-router-dom' -type DivRef = ForwardedRef - -// remove possibility of disabling links for now. if we put it back, make sure -// to forwardRef on LinkItem so the disabled tooltip can work -type LinkitemProps = Omit & { to: string } - -export const DropdownMenu = { - Root, - Trigger, - Portal, - // don't need to forward ref here for a particular reason but Radix gives a - // big angry warning if we don't - Content: forwardRef(({ className, ...props }: DropdownMenuContentProps, ref: DivRef) => ( - e.preventDefault()} - className={cn('DropdownMenuContent', className)} - ref={ref} - /> - )), - // need to forward ref because of tooltips on disabled menu buttons - Item: forwardRef(({ className, ...props }: DropdownMenuItemProps, ref: DivRef) => ( - - )), - LinkItem: ({ className, children, to, ...props }: LinkitemProps) => ( - - {children} - - ), +export const Root = Menu + +export const Trigger = MenuButton + +type ContentProps = { + className?: string + children: ReactNode + anchor?: MenuItemsProps['anchor'] + /** Spacing in px, passed as --anchor-gap */ + gap?: 8 } + +export function Content({ className, children, anchor = 'bottom end', gap }: ContentProps) { + return ( + + {children} + + ) +} + +type LinkItemProps = { className?: string; to: string; children: ReactNode } + +export function LinkItem({ className, to, children }: LinkItemProps) { + return ( + + + {children} + + + ) +} + +type ButtonRef = ForwardedRef +type ItemProps = { + className?: string + onSelect?: () => void + children: ReactNode + disabled?: boolean +} + +// need to forward ref because of tooltips on disabled menu buttons +export const Item = forwardRef( + ({ className, onSelect, children, disabled }: ItemProps, ref: ButtonRef) => ( + + + + ) +) diff --git a/app/ui/styles/components/menu-button.css b/app/ui/styles/components/menu-button.css index 526a881ca..2a88a8723 100644 --- a/app/ui/styles/components/menu-button.css +++ b/app/ui/styles/components/menu-button.css @@ -7,10 +7,10 @@ */ .DropdownMenuContent { - @apply z-30 min-w-36 rounded border p-0 bg-raise border-secondary; + @apply z-popover min-w-36 rounded border p-0 bg-raise border-secondary; & .DropdownMenuItem { - @apply block cursor-pointer select-none border-b py-2 pl-3 pr-6 text-left text-sans-md text-secondary border-secondary last:border-b-0; + @apply block w-full cursor-pointer select-none border-b py-2 pl-3 pr-6 text-left text-sans-md text-secondary border-secondary last:border-b-0; &.destructive { @apply text-destructive; @@ -24,8 +24,7 @@ @apply text-destructive-disabled; } - &[data-highlighted] { - /* background: hsl(211, 81%, 36%); */ + &[data-focus] { outline: none; @apply bg-tertiary; } diff --git a/package-lock.json b/package-lock.json index 395353411..24e9e4015 100644 --- a/package-lock.json +++ b/package-lock.json @@ -15,7 +15,6 @@ "@oxide/design-system": "^1.4.6", "@radix-ui/react-accordion": "^1.2.0", "@radix-ui/react-dialog": "^1.0.5", - "@radix-ui/react-dropdown-menu": "^2.0.6", "@radix-ui/react-focus-guards": "1.0.1", "@radix-ui/react-tabs": "^1.1.0", "@react-aria/live-announcer": "^3.3.4", @@ -2472,29 +2471,6 @@ } } }, - "node_modules/@radix-ui/react-arrow": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/@radix-ui/react-arrow/-/react-arrow-1.1.0.tgz", - "integrity": "sha512-FmlW1rCg7hBpEBwFbjHwCW6AmWLQM6g/v0Sn8XbP9NvmSZ2San1FpQeyPtufzOMSIx7Y4dzjlHoifhp+7NkZhw==", - "license": "MIT", - "dependencies": { - "@radix-ui/react-primitive": "2.0.0" - }, - "peerDependencies": { - "@types/react": "*", - "@types/react-dom": "*", - "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", - "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" - }, - "peerDependenciesMeta": { - "@types/react": { - "optional": true - }, - "@types/react-dom": { - "optional": true - } - } - }, "node_modules/@radix-ui/react-collapsible": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/@radix-ui/react-collapsible/-/react-collapsible-1.1.0.tgz", @@ -2699,35 +2675,6 @@ } } }, - "node_modules/@radix-ui/react-dropdown-menu": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/@radix-ui/react-dropdown-menu/-/react-dropdown-menu-2.1.1.tgz", - "integrity": "sha512-y8E+x9fBq9qvteD2Zwa4397pUVhYsh9iq44b5RD5qu1GMJWBCBuVg1hMyItbc6+zH00TxGRqd9Iot4wzf3OoBQ==", - "license": "MIT", - "dependencies": { - "@radix-ui/primitive": "1.1.0", - "@radix-ui/react-compose-refs": "1.1.0", - "@radix-ui/react-context": "1.1.0", - "@radix-ui/react-id": "1.1.0", - "@radix-ui/react-menu": "2.1.1", - "@radix-ui/react-primitive": "2.0.0", - "@radix-ui/react-use-controllable-state": "1.1.0" - }, - "peerDependencies": { - "@types/react": "*", - "@types/react-dom": "*", - "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", - "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" - }, - "peerDependenciesMeta": { - "@types/react": { - "optional": true - }, - "@types/react-dom": { - "optional": true - } - } - }, "node_modules/@radix-ui/react-focus-guards": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/@radix-ui/react-focus-guards/-/react-focus-guards-1.0.1.tgz", @@ -2789,118 +2736,6 @@ } } }, - "node_modules/@radix-ui/react-menu": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/@radix-ui/react-menu/-/react-menu-2.1.1.tgz", - "integrity": "sha512-oa3mXRRVjHi6DZu/ghuzdylyjaMXLymx83irM7hTxutQbD+7IhPKdMdRHD26Rm+kHRrWcrUkkRPv5pd47a2xFQ==", - "license": "MIT", - "dependencies": { - "@radix-ui/primitive": "1.1.0", - "@radix-ui/react-collection": "1.1.0", - "@radix-ui/react-compose-refs": "1.1.0", - "@radix-ui/react-context": "1.1.0", - "@radix-ui/react-direction": "1.1.0", - "@radix-ui/react-dismissable-layer": "1.1.0", - "@radix-ui/react-focus-guards": "1.1.0", - "@radix-ui/react-focus-scope": "1.1.0", - "@radix-ui/react-id": "1.1.0", - "@radix-ui/react-popper": "1.2.0", - "@radix-ui/react-portal": "1.1.1", - "@radix-ui/react-presence": "1.1.0", - "@radix-ui/react-primitive": "2.0.0", - "@radix-ui/react-roving-focus": "1.1.0", - "@radix-ui/react-slot": "1.1.0", - "@radix-ui/react-use-callback-ref": "1.1.0", - "aria-hidden": "^1.1.1", - "react-remove-scroll": "2.5.7" - }, - "peerDependencies": { - "@types/react": "*", - "@types/react-dom": "*", - "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", - "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" - }, - "peerDependenciesMeta": { - "@types/react": { - "optional": true - }, - "@types/react-dom": { - "optional": true - } - } - }, - "node_modules/@radix-ui/react-menu/node_modules/@radix-ui/react-focus-guards": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/@radix-ui/react-focus-guards/-/react-focus-guards-1.1.0.tgz", - "integrity": "sha512-w6XZNUPVv6xCpZUqb/yN9DL6auvpGX3C/ee6Hdi16v2UUy25HV2Q5bcflsiDyT/g5RwbPQ/GIT1vLkeRb+ITBw==", - "license": "MIT", - "peerDependencies": { - "@types/react": "*", - "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" - }, - "peerDependenciesMeta": { - "@types/react": { - "optional": true - } - } - }, - "node_modules/@radix-ui/react-menu/node_modules/react-remove-scroll": { - "version": "2.5.7", - "resolved": "https://registry.npmjs.org/react-remove-scroll/-/react-remove-scroll-2.5.7.tgz", - "integrity": "sha512-FnrTWO4L7/Bhhf3CYBNArEG/yROV0tKmTv7/3h9QCFvH6sndeFf1wPqOcbFVu5VAulS5dV1wGT3GZZ/1GawqiA==", - "license": "MIT", - "dependencies": { - "react-remove-scroll-bar": "^2.3.4", - "react-style-singleton": "^2.2.1", - "tslib": "^2.1.0", - "use-callback-ref": "^1.3.0", - "use-sidecar": "^1.1.2" - }, - "engines": { - "node": ">=10" - }, - "peerDependencies": { - "@types/react": "^16.8.0 || ^17.0.0 || ^18.0.0", - "react": "^16.8.0 || ^17.0.0 || ^18.0.0" - }, - "peerDependenciesMeta": { - "@types/react": { - "optional": true - } - } - }, - "node_modules/@radix-ui/react-popper": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/@radix-ui/react-popper/-/react-popper-1.2.0.tgz", - "integrity": "sha512-ZnRMshKF43aBxVWPWvbj21+7TQCvhuULWJ4gNIKYpRlQt5xGRhLx66tMp8pya2UkGHTSlhpXwmjqltDYHhw7Vg==", - "license": "MIT", - "dependencies": { - "@floating-ui/react-dom": "^2.0.0", - "@radix-ui/react-arrow": "1.1.0", - "@radix-ui/react-compose-refs": "1.1.0", - "@radix-ui/react-context": "1.1.0", - "@radix-ui/react-primitive": "2.0.0", - "@radix-ui/react-use-callback-ref": "1.1.0", - "@radix-ui/react-use-layout-effect": "1.1.0", - "@radix-ui/react-use-rect": "1.1.0", - "@radix-ui/react-use-size": "1.1.0", - "@radix-ui/rect": "1.1.0" - }, - "peerDependencies": { - "@types/react": "*", - "@types/react-dom": "*", - "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", - "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" - }, - "peerDependenciesMeta": { - "@types/react": { - "optional": true - }, - "@types/react-dom": { - "optional": true - } - } - }, "node_modules/@radix-ui/react-portal": { "version": "1.1.1", "resolved": "https://registry.npmjs.org/@radix-ui/react-portal/-/react-portal-1.1.1.tgz", @@ -3117,48 +2952,6 @@ } } }, - "node_modules/@radix-ui/react-use-rect": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/@radix-ui/react-use-rect/-/react-use-rect-1.1.0.tgz", - "integrity": "sha512-0Fmkebhr6PiseyZlYAOtLS+nb7jLmpqTrJyv61Pe68MKYW6OWdRE2kI70TaYY27u7H0lajqM3hSMMLFq18Z7nQ==", - "license": "MIT", - "dependencies": { - "@radix-ui/rect": "1.1.0" - }, - "peerDependencies": { - "@types/react": "*", - "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" - }, - "peerDependenciesMeta": { - "@types/react": { - "optional": true - } - } - }, - "node_modules/@radix-ui/react-use-size": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/@radix-ui/react-use-size/-/react-use-size-1.1.0.tgz", - "integrity": "sha512-XW3/vWuIXHa+2Uwcc2ABSfcCledmXhhQPlGbfcRXbiUQI5Icjcg19BGCZVKKInYbvUCut/ufbbLLPFC5cbb1hw==", - "license": "MIT", - "dependencies": { - "@radix-ui/react-use-layout-effect": "1.1.0" - }, - "peerDependencies": { - "@types/react": "*", - "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" - }, - "peerDependenciesMeta": { - "@types/react": { - "optional": true - } - } - }, - "node_modules/@radix-ui/rect": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/@radix-ui/rect/-/rect-1.1.0.tgz", - "integrity": "sha512-A9+lCBZoaMJlVKcRBz2YByCG+Cp2t6nAnMnNba+XiWxnj6r4JUFqfsgwocMBZU9LPtdxC6wB56ySYpc7LQIoJg==", - "license": "MIT" - }, "node_modules/@react-aria/breadcrumbs": { "version": "3.5.16", "resolved": "https://registry.npmjs.org/@react-aria/breadcrumbs/-/breadcrumbs-3.5.16.tgz", diff --git a/package.json b/package.json index 35b52c362..de52f1cad 100644 --- a/package.json +++ b/package.json @@ -38,7 +38,6 @@ "@oxide/design-system": "^1.4.6", "@radix-ui/react-accordion": "^1.2.0", "@radix-ui/react-dialog": "^1.0.5", - "@radix-ui/react-dropdown-menu": "^2.0.6", "@radix-ui/react-focus-guards": "1.0.1", "@radix-ui/react-tabs": "^1.1.0", "@react-aria/live-announcer": "^3.3.4", diff --git a/tailwind.config.js b/tailwind.config.js index 17b8108be..be83d8b78 100644 --- a/tailwind.config.js +++ b/tailwind.config.js @@ -48,6 +48,7 @@ module.exports = { modal: '40', sideModalDropdown: '40', sideModal: '30', + topBarPopover: '25', topBar: '20', popover: '10', contentDropdown: '10',