From 7f8038d2b3f27fdfceb2517d23a7acdb609fc1c8 Mon Sep 17 00:00:00 2001 From: Felix Thape Date: Tue, 31 Dec 2024 15:06:06 +0100 Subject: [PATCH] feat: add menu hover effects and update styling --- landing-page/components/Header.tsx | 70 ++++++++-------- lib/components/user-input/Menu.tsx | 81 +++++++++++++------ lib/components/user-input/MultiSelect.tsx | 38 +++++---- tasks/components/UserMenu.tsx | 32 +++----- .../layout/property/PropertyList.tsx | 2 +- 5 files changed, 118 insertions(+), 105 deletions(-) diff --git a/landing-page/components/Header.tsx b/landing-page/components/Header.tsx index 057a17dfe..22ad707da 100644 --- a/landing-page/components/Header.tsx +++ b/landing-page/components/Header.tsx @@ -1,7 +1,7 @@ import { Span } from '@helpwave/common/components/Span' import { Helpwave } from '@helpwave/common/icons/Helpwave' import { tw } from '@helpwave/common/twind' -import { Menu as MenuIcon, X } from 'lucide-react' +import { ChevronDown, Menu as MenuIcon, X } from 'lucide-react' import { useState } from 'react' import Link from 'next/link' import { Menu, MenuItem } from '@helpwave/common/components/user-input/Menu' @@ -9,6 +9,7 @@ import type { Languages } from '@helpwave/common/hooks/useLanguage' import { useTranslation } from '@helpwave/common/hooks/useTranslation' import { MarkdownInterpreter } from '@helpwave/common/components/MarkdownInterpreter' import { Chip } from '@helpwave/common/components/ChipList' +import { tx } from '@twind/core' const homeURL = '/' @@ -107,11 +108,12 @@ const Header = () => { return ( <> -
+
@@ -179,7 +172,7 @@ const Header = () => {
@@ -204,10 +197,11 @@ const Header = () => { ) : ( alignment="tl" trigger={(onClick, ref) => ( -
+
{translation[name]} +
)}> {subpage.map(({ @@ -216,14 +210,14 @@ const Header = () => { external: subPageExternal }) => ( - setNavbarOpen(false)} - href={subPageExternal ? subPageUrl : url + subPageUrl}> - - - {translation[subPageName]} - - - + setNavbarOpen(false)} + href={subPageExternal ? subPageUrl : url + subPageUrl}> + + + {translation[subPageName]} + + + ))} )} diff --git a/lib/components/user-input/Menu.tsx b/lib/components/user-input/Menu.tsx index ca5e38c31..f64295264 100644 --- a/lib/components/user-input/Menu.tsx +++ b/lib/components/user-input/Menu.tsx @@ -1,38 +1,70 @@ -import { useRef, useState, type PropsWithChildren, type ReactNode, type RefObject, useEffect } from 'react' -import { tw, tx } from '../../twind' +import { + useRef, + useState, + useEffect +} from 'react' +import type { + AriaAttributes, + PropsWithChildren, + ReactNode, + RefObject, HTMLAttributes +} from 'react' +import { apply } from '@twind/core' +import { tx } from '../../twind' import { useOutsideClick } from '../../hooks/useOutsideClick' type MenuProps = PropsWithChildren<{ + /** + * The builder for the trigger element. Assign the ref and onClick to your element that should be used as a trigger. + * + * Accessibility: ensure your trigger element is either a button or has the role="button" in addition to a tabIndex={0} or value > 0 + */ trigger: (onClick: () => void, ref: RefObject) => ReactNode, /** * @default 'tl' */ alignment?: 'tl' | 'tr' | 'bl' | 'br' | '_l' | '_r' | 't_' | 'b_', showOnHover?: boolean, - menuClassName?: string + menuClassName?: string, + containerClassName?: string }> -export type MenuItemProps = { +export type MenuItemProps = AriaAttributes & Pick, 'role'> & { onClick?: () => void, alignment?: 'left' | 'right', + isDisabled?: boolean, className?: string } + const MenuItem = ({ children, onClick, alignment = 'left', - className -}: PropsWithChildren) => ( -
- {children} -
-) + isDisabled = false, + className = '', + role = 'menuitem', + ...restProps +}: PropsWithChildren) => { + const isClickable = onClick !== undefined + + return ( +
+ {children} +
+ ) +} // TODO: it is quite annoying that the type for the ref has to be specified manually, is there some solution around this? /** @@ -44,6 +76,7 @@ const Menu = ({ alignment = 'tl', showOnHover = false, menuClassName = '', + containerClassName = '', }: MenuProps) => { const [open, setOpen] = useState(false) const [timer, setTimer] = useState() @@ -76,19 +109,21 @@ const Menu = ({ return (
{trigger(() => setOpen(!open), triggerRef)} {open ? (
e.stopPropagation()} - className={tx('absolute top-full mt-1 py-2 w-60 rounded-lg bg-white ring-1 ring-slate-900/5 text-sm leading-6 font-semibold text-slate-700 shadow-md z-[1]', { - ' top-[8px]': alignment[0] === 't', - ' bottom-[8px]': alignment[0] === 'b', - ' left-[-8px]': alignment[1] === 'l', - ' right-[-8px]': alignment[1] === 'r', - }, menuClassName)}> + className={tx(apply('absolute top-full mt-1 py-2 w-60 rounded-lg bg-white ring-1 ring-slate-900/5 shadow-md z-[1]'), { + '@(top-[8px])': alignment[0] === 't', + '@(bottom-[8px])': alignment[0] === 'b', + '@(left-[-8px])': alignment[1] === 'l', + '@(right-[-8px])': alignment[1] === 'r', + }, menuClassName)} + role="menu" + > {children}
) : null} diff --git a/lib/components/user-input/MultiSelect.tsx b/lib/components/user-input/MultiSelect.tsx index ddc218c00..c3dfabc99 100644 --- a/lib/components/user-input/MultiSelect.tsx +++ b/lib/components/user-input/MultiSelect.tsx @@ -100,7 +100,8 @@ export const MultiSelect = ({ return (
{label && ( -
)} {filteredOptions.map((option, index) => ( - -
{ - if (!option.disabled) { - onChange(options.map(value => value.value === option.value ? ({ - ...option, - selected: !value.selected - }) : value)) - } - }} - > - - {option.label} -
+ { + if (!option.disabled) { + onChange(options.map(value => value.value === option.value ? ({ + ...option, + selected: !value.selected + }) : value)) + } + }} + role="menuitemcheckbox" + isDisabled={option.disabled} + > + + {option.label} ))} diff --git a/tasks/components/UserMenu.tsx b/tasks/components/UserMenu.tsx index 0df027c6a..01fd0a72b 100644 --- a/tasks/components/UserMenu.tsx +++ b/tasks/components/UserMenu.tsx @@ -1,6 +1,5 @@ import { useState } from 'react' import Link from 'next/link' -import { useRouter } from 'next/router' import { tw } from '@helpwave/common/twind' import { Menu, MenuItem } from '@helpwave/common/components/user-input/Menu' import { LanguageModal } from '@helpwave/common/components/modals/LanguageModal' @@ -52,7 +51,6 @@ export const UserMenu = ({ const translation = useTranslation(defaultUserMenuTranslations, overwriteTranslation) const [isLanguageModalOpen, setLanguageModalOpen] = useState(false) const { user, signOut } = useAuth() - const router = useRouter() if (!user) return null @@ -75,27 +73,15 @@ export const UserMenu = ({
)}> - {translation.profile} -
setLanguageModalOpen(true)}>{translation.language}
-
router.push('/templates')}> - {translation.taskTemplates} -
-
router.push('/properties')}> - {translation.properties} -
-
router.push('/organizations')}> - {translation.organizations} -
-
router.push('/invitations')}> - {translation.invitations} -
-
signOut()} - > - {translation.signOut} -
+ {translation.profile} + setLanguageModalOpen(true)}>{translation.language} + {translation.taskTemplates} + {translation.properties} + {translation.organizations} + {translation.invitations} + signOut()}> + {translation.signOut} +
) diff --git a/tasks/components/layout/property/PropertyList.tsx b/tasks/components/layout/property/PropertyList.tsx index 4010a015c..22271a998 100644 --- a/tasks/components/layout/property/PropertyList.tsx +++ b/tasks/components/layout/property/PropertyList.tsx @@ -127,7 +127,7 @@ export const PropertyList = ({ addOrUpdatePropertyMutation.mutate({ previous: attachedProperty, update: attachedProperty, fieldType: property.fieldType }) updateViewRulesMutation.mutate({ subjectId, appendToAlwaysInclude: [property.id] }) }} - className={tw('rounded-md cursor-pointer')} + className={tw('rounded-md')} > {property.name}