Skip to content

Commit

Permalink
refactor(ui): make popover hoverable (#694)
Browse files Browse the repository at this point in the history
  • Loading branch information
mkue authored Jan 4, 2024
1 parent cedeea2 commit 91dc333
Show file tree
Hide file tree
Showing 4 changed files with 93 additions and 39 deletions.
6 changes: 3 additions & 3 deletions admin/src/collections/Contributions.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -48,9 +48,9 @@ export function buildContributionsCollection(
dataType: 'string',
name: 'Currency',
enumValues: {
chf: 'CHF',
usd: 'USD',
eur: 'EUR',
CHF: 'CHF',
USD: 'USD',
EUR: 'EUR',
},
validation: { required: true },
},
Expand Down
90 changes: 72 additions & 18 deletions ui/src/components/popover.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,30 +2,84 @@

import * as PopoverPrimitive from '@radix-ui/react-popover';
import * as React from 'react';

import { FC, useContext, useState } from 'react';
import { cn } from '../lib/utils';

const Popover = PopoverPrimitive.Root;
const PopoverOnOpenChangeContext = React.createContext({
onOpenChange: (_open: boolean) => {},
timeoutId: { current: null } as React.MutableRefObject<ReturnType<typeof setTimeout> | null>,
openDelay: 0,
closeDelay: 200,
});

const PopoverTrigger = PopoverPrimitive.Trigger;
const Popover: FC<React.ComponentProps<typeof PopoverPrimitive.Root> & { openDelay?: number; closeDelay?: number }> = ({
open,
onOpenChange,
openDelay = 0,
closeDelay = 200,
...props
}) => {
const [defaultOpen, defaultOnOpenChange] = useState(false);
const timeoutId = React.useRef(null);

const PopoverContent = React.forwardRef<
React.ElementRef<typeof PopoverPrimitive.Content>,
React.ComponentPropsWithoutRef<typeof PopoverPrimitive.Content>
>(({ className, align = 'center', sideOffset = 4, ...props }, ref) => (
<PopoverPrimitive.Portal>
<PopoverPrimitive.Content
ref={ref}
align={align}
sideOffset={sideOffset}
className={cn(
'bg-popover text-popover-foreground data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0 data-[state=closed]:zoom-out-95 data-[state=open]:zoom-in-95 data-[side=bottom]:slide-in-from-top-2 data-[side=left]:slide-in-from-right-2 data-[side=right]:slide-in-from-left-2 data-[side=top]:slide-in-from-bottom-2 z-50 w-72 rounded-md border p-4 shadow-md outline-none',
className,
)}
return (
<PopoverOnOpenChangeContext.Provider
value={{
onOpenChange: onOpenChange ?? defaultOnOpenChange,
timeoutId,
openDelay,
closeDelay,
}}
>
<PopoverPrimitive.Root {...props} open={open ?? defaultOpen} onOpenChange={onOpenChange ?? defaultOnOpenChange} />
</PopoverOnOpenChangeContext.Provider>
);
};

const PopoverTrigger: React.FC<any> = (props) => {
const { onOpenChange, timeoutId, openDelay, closeDelay } = useContext(PopoverOnOpenChangeContext);

return (
<PopoverPrimitive.Trigger
{...props}
onMouseEnter={() => {
timeoutId.current && clearTimeout(timeoutId.current);
setTimeout(() => onOpenChange(true), openDelay);
}}
onMouseLeave={() => {
timeoutId.current = setTimeout(() => onOpenChange(false), closeDelay);
}}
/>
</PopoverPrimitive.Portal>
));
);
};

const PopoverContent = React.forwardRef<
React.ElementRef<typeof PopoverPrimitive.Content>,
React.PropsWithoutRef<React.ComponentProps<typeof PopoverPrimitive.Content>>
>(({ align = 'center', sideOffset = 4, className, ...props }, ref) => {
const { onOpenChange, timeoutId } = useContext(PopoverOnOpenChangeContext);

return (
<PopoverPrimitive.Portal>
<PopoverPrimitive.Content
ref={ref}
align={align}
sideOffset={sideOffset}
onMouseEnter={() => {
timeoutId.current && clearTimeout(timeoutId.current);
}}
onMouseLeave={() => {
timeoutId.current = setTimeout(() => onOpenChange(false), 300);
}}
className={cn(
'bg-popover text-popover-foreground data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0 data-[state=closed]:zoom-out-95 data-[state=open]:zoom-in-95 data-[side=bottom]:slide-in-from-top-2 data-[side=left]:slide-in-from-right-2 data-[side=right]:slide-in-from-left-2 data-[side=top]:slide-in-from-bottom-2 z-50 w-72 rounded-md border p-4 shadow-md outline-none',
className,
)}
{...props}
/>
</PopoverPrimitive.Portal>
);
});
PopoverContent.displayName = PopoverPrimitive.Content.displayName;

export { Popover, PopoverContent, PopoverTrigger };
Original file line number Diff line number Diff line change
Expand Up @@ -3,9 +3,9 @@ import { LanguageCode } from '@socialincome/shared/src/types/language';
import { Translator } from '@socialincome/shared/src/utils/i18n';
import {
BaseContainer,
HoverCard,
HoverCardContent,
HoverCardTrigger,
Popover,
PopoverContent,
PopoverTrigger,
Table,
TableBody,
TableCell,
Expand All @@ -25,11 +25,11 @@ export async function Video({ lang }: { lang: LanguageCode }) {
<VimeoVideo videoId={Number(translator.t('video.video-id'))} />
</div>
<div className="mt-2 self-end">
<HoverCard openDelay={0} closeDelay={200}>
<HoverCardTrigger>
<Popover openDelay={0} closeDelay={200}>
<PopoverTrigger>
<Typography>{translator.t('video.credits-title')}</Typography>
</HoverCardTrigger>
<HoverCardContent align="end" className="w-96">
</PopoverTrigger>
<PopoverContent align="end" className="w-96">
<Table>
<TableBody>
{translator
Expand All @@ -42,8 +42,8 @@ export async function Video({ lang }: { lang: LanguageCode }) {
))}
</TableBody>
</Table>
</HoverCardContent>
</HoverCard>
</PopoverContent>
</Popover>
</div>
</BaseContainer>
);
Expand Down
18 changes: 9 additions & 9 deletions website/src/components/navbar/navbar-client.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -17,9 +17,9 @@ import {
Collapsible,
CollapsibleContent,
CollapsibleTrigger,
HoverCard,
HoverCardContent,
HoverCardTrigger,
Popover,
PopoverContent,
PopoverTrigger,
Typography,
} from '@socialincome/ui';
import _ from 'lodash';
Expand Down Expand Up @@ -119,14 +119,14 @@ export function NavbarClient({
</Button>
</Link>
) : (
<HoverCard key={index} openDelay={0} closeDelay={200}>
<HoverCardTrigger asChild>
<Popover key={index} openDelay={100} closeDelay={200}>
<PopoverTrigger asChild>
<Button variant="ghost" className="flex items-center space-x-2 py-6">
<Typography size="xl">{section.title}</Typography>
{(section.links?.length ?? 0) > 0 && <ChevronDownIcon className="h-4 w-4" />}
</Button>
</HoverCardTrigger>
<HoverCardContent asChild alignOffset={20} className="bg-popover w-56 p-0">
</PopoverTrigger>
<PopoverContent asChild alignOffset={20} className="bg-popover w-56 p-0">
<ul className="divide-muted divide-y">
{section.links?.map((link, index) => (
<li key={index} className="hover:bg-popover-muted px-8 py-3">
Expand All @@ -138,8 +138,8 @@ export function NavbarClient({
</li>
))}
</ul>
</HoverCardContent>
</HoverCard>
</PopoverContent>
</Popover>
)}
</div>
);
Expand Down

0 comments on commit 91dc333

Please sign in to comment.