Files
Compass/web/components/comments/dropdown-menu.tsx
Okechi Jones-Williams e115df8e11 [Onboarding Flow] Answer compatibility questions in the test suite for onboarding (#35)
* Fixed Type errors

* Added Database checks to the onboarding flow

* Updated Onboarding flow
Changed type ChildrenExpectation so that it can be used for database verification

* Added compatibility page setup
Added more compatibility questions

* Finished up the onboarding flow suite
Added compatibility question tests and verifications
Updated tests to cover Keywords and Headline changes recently made
Updated tests to cover all of the big5 personality traits

* Apply suggestions from code review

---------

Co-authored-by: Martin Braquet <martin.braquet@gmail.com>
2026-03-04 11:05:56 +01:00

175 lines
5.3 KiB
TypeScript

import {flip, offset, shift, useFloating} from '@floating-ui/react'
import {Popover, PopoverButton, PopoverPanel, Transition} from '@headlessui/react'
import {ChevronDownIcon, ChevronUpIcon, EllipsisHorizontalIcon} from '@heroicons/react/24/solid'
import clsx from 'clsx'
import {toKey} from 'common/parsing'
import {Fragment, ReactNode} from 'react'
import {Col} from 'web/components/layout/col'
import {Row} from 'web/components/layout/row'
import {useT} from 'web/lib/locale'
export type DropdownItem = {
name: string
icon?: ReactNode
onClick: () => void | Promise<void>
}
export default function DropdownMenu(props: {
items: DropdownItem[]
icon?: ReactNode
menuWidth?: string
buttonClass?: string
className?: string
menuItemsClass?: string
buttonDisabled?: boolean
selectedItemName?: string
closeOnClick?: boolean
withinOverflowContainer?: boolean
buttonContent?: (open: boolean) => ReactNode
}) {
const {
items,
menuItemsClass,
menuWidth,
buttonClass,
className,
buttonDisabled,
selectedItemName,
closeOnClick,
withinOverflowContainer,
buttonContent,
} = props
const {refs, floatingStyles} = useFloating({
strategy: withinOverflowContainer ? 'fixed' : 'absolute',
middleware: [offset(8), flip(), shift({padding: 8})],
})
const icon = props.icon ?? <EllipsisHorizontalIcon className="h-5 w-5" aria-hidden="true" />
return (
<Popover className={clsx('relative inline-block text-left', className)}>
{({open, close}) => (
<>
<PopoverButton
data-testid="profile-compatibility-dropdown"
ref={refs.setReference}
className={clsx('text-ink-500 hover-bold flex items-center', buttonClass)}
onClick={(e: React.MouseEvent<HTMLButtonElement>) => e.stopPropagation()}
disabled={buttonDisabled}
>
<span className="sr-only">Open options</span>
{buttonContent ? buttonContent(open) : icon}
</PopoverButton>
<AnimationOrNothing show={open} animate={!withinOverflowContainer}>
<PopoverPanel
ref={refs.setFloating}
style={floatingStyles}
className={clsx(
'bg-canvas-0 ring-ink-1000 z-30 mt-2 rounded-md shadow-lg ring-1 ring-opacity-5 focus:outline-none',
menuWidth ?? 'w-34',
menuItemsClass,
'py-1',
)}
>
{items.map((item) => (
<button
key={item.name}
onClick={(e: React.MouseEvent<HTMLButtonElement>) => {
e.stopPropagation()
e.preventDefault()
item.onClick()
if (closeOnClick) close()
}}
className={clsx(
selectedItemName && item.name === selectedItemName
? 'bg-primary-100'
: 'hover:bg-canvas-100 hover:text-ink-900',
'text-ink-700 flex w-full gap-2 px-4 py-2 text-left text-sm rounded-md',
)}
>
{item.icon && <div className="w-5">{item.icon}</div>}
{item.name}
</button>
))}
</PopoverPanel>
</AnimationOrNothing>
</>
)}
</Popover>
)
}
export const AnimationOrNothing = (props: {
animate: boolean
show: boolean
children: ReactNode
}) => {
return props.animate ? (
<Transition
as={Fragment}
enter="transition ease-out duration-100"
enterFrom="transform opacity-0 scale-95"
enterTo="transform opacity-100 scale-100"
leave="transition ease-in duration-75"
leaveFrom="transform opacity-100 scale-100"
leaveTo="transform opacity-0 scale-95"
show={props.show}
>
{props.children}
</Transition>
) : (
<>{props.children}</>
)
}
export function DropdownOptions(props: {
items: Record<string, any>
onClick: (item: any) => void
activeKey: string
translationPrefix?: string
}) {
const {items, onClick, activeKey, translationPrefix} = props
const t = useT()
const translateOption = (key: string, value: string) => {
if (!translationPrefix) return value
return t(`${translationPrefix}.${toKey(key)}`, value)
}
return (
<Col>
{Object.entries(items).map(([key, item]) => (
<button
key={key}
onClick={(e: React.MouseEvent<HTMLButtonElement>) => {
e.stopPropagation()
e.preventDefault()
onClick(key)
}}
className={clsx(
key === activeKey ? 'bg-primary-100' : 'hover:bg-canvas-100 hover:text-ink-900',
'text-ink-700 flex w-full gap-2 px-4 py-2 text-left text-sm rounded-md',
)}
>
{item.icon && <div className="w-5">{item.icon}</div>}
{translateOption(key, item.label ?? item)}
</button>
))}
</Col>
)
}
export function DropdownButton(props: {open: boolean; content: ReactNode}) {
const {open, content} = props
return (
<Row className="hover:text-ink-700 items-center gap-0.5 transition-all">
{content}
<span className="text-ink-400">
{open ? <ChevronUpIcon className="h-4 w-4" /> : <ChevronDownIcon className="h-4 w-4" />}
</span>
</Row>
)
}