mirror of
https://github.com/CompassConnections/Compass.git
synced 2026-03-25 10:02:27 -04:00
* 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>
175 lines
5.3 KiB
TypeScript
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>
|
|
)
|
|
}
|