diff --git a/app/(geist)/geist/dropdown-menu/_sections/dropdown-menu-default.tsx b/app/(geist)/geist/dropdown-menu/_sections/dropdown-menu-default.tsx
index e5ccc35..65ce4ce 100644
--- a/app/(geist)/geist/dropdown-menu/_sections/dropdown-menu-default.tsx
+++ b/app/(geist)/geist/dropdown-menu/_sections/dropdown-menu-default.tsx
@@ -12,7 +12,11 @@ export default function DropdownMenuDefault() {
Open
- console.log(false)}>One
+ One
+ Two
+ One
+ Two
+ One
Two
diff --git a/components/ui/primitives/popper/popper-content.tsx b/components/ui/primitives/popper/popper-content.tsx
index fd52dd0..88a0659 100644
--- a/components/ui/primitives/popper/popper-content.tsx
+++ b/components/ui/primitives/popper/popper-content.tsx
@@ -8,17 +8,66 @@ import { useOutsideClick } from '@/hooks/use-ui';
import { cn } from '@/utils/lib';
import React from 'react';
import ReactFocusLock from 'react-focus-lock';
+import { POPPER_ITEM_SELECTOR } from '../selectors';
export function PopperContent(props: PopperContentProps) {
const { children, className, ...etc } = props;
- const { isOpen, popperStyle, closePopper, id } = usePopper();
+ const {
+ isOpen,
+ popperStyle,
+ closePopper,
+ activeTrigger,
+ highlightedIndex,
+ setHighlightedIndex,
+ highlight,
+ id,
+ } = usePopper();
const ref = useOutsideClick({ action: closePopper });
+ function handleKeyDown(event: React.KeyboardEvent) {
+ if (!ref.current) return;
+ if (event.key === 'Escape') {
+ closePopper();
+ }
+ if (event.key === 'ArrowUp' || event.key === 'ArrowDown') {
+ event.preventDefault();
+ const direction: 'next' | 'previous' =
+ event.code === 'ArrowUp' ? 'previous' : 'next';
+
+ const menuItems = Array.from(
+ ref.current.querySelectorAll(POPPER_ITEM_SELECTOR),
+ );
+
+ let nextIndex;
+ switch (direction) {
+ case 'next':
+ nextIndex =
+ highlightedIndex === undefined ||
+ highlightedIndex === menuItems.length - 1
+ ? menuItems.indexOf(menuItems[menuItems.length - 1])
+ : highlightedIndex + 1;
+ break;
+ default:
+ nextIndex =
+ highlightedIndex === undefined || highlightedIndex === 0
+ ? 0
+ : highlightedIndex - 1;
+ break;
+ }
+ console.log(highlightedIndex);
+ setHighlightedIndex(nextIndex);
+ highlight(menuItems[nextIndex] as HTMLElement);
+ }
+ }
+
return createPortal(
-
+ activeTrigger?.focus()}>
{isOpen && popperStyle && (
-
+ activeTrigger?.focus()}
+ >
{children}
diff --git a/components/ui/primitives/popper/popper-context.tsx b/components/ui/primitives/popper/popper-context.tsx
index 8957607..c8d848e 100644
--- a/components/ui/primitives/popper/popper-context.tsx
+++ b/components/ui/primitives/popper/popper-context.tsx
@@ -3,7 +3,10 @@
import React, { CSSProperties, useState } from 'react';
import { PopperContextProps } from './popper.types';
import { useRestrict } from '@/hooks/use-ui';
-import { POPPER_CONTENT_SELECTOR } from '@/components/ui/primitives/selectors';
+import {
+ POPPER_CONTENT_SELECTOR,
+ POPPER_ITEM_SELECTOR,
+} from '@/components/ui/primitives/selectors';
const PopperContext = React.createContext(null);
@@ -17,7 +20,9 @@ export function usePopper() {
export function PopperProvider({ children }: { children: React.ReactNode }) {
const [isOpen, setIsOpen] = React.useState(false);
const [popperStyle, setPopperStyle] = useState({});
- const [highlightedIndex, setHighlightedIndex] = React.useState(-1);
+ const [highlightedIndex, setHighlightedIndex] = React.useState<
+ number | undefined
+ >(undefined);
const [highlightedItem, setHighlightedItem] =
React.useState(null);
@@ -29,8 +34,15 @@ export function PopperProvider({ children }: { children: React.ReactNode }) {
function highlight(element: HTMLElement | null) {
if (element) {
+ const rootElement = element.closest(
+ POPPER_CONTENT_SELECTOR,
+ ) as HTMLElement;
+ const items = Array.from(
+ rootElement.querySelectorAll(POPPER_ITEM_SELECTOR),
+ );
setHighlightedItem(element);
element?.focus();
+ setHighlightedIndex(items.indexOf(element));
} else {
setHighlightedItem(null);
(document.querySelector(POPPER_CONTENT_SELECTOR) as HTMLElement).focus();
@@ -46,17 +58,17 @@ export function PopperProvider({ children }: { children: React.ReactNode }) {
setIsOpen((prevState) => !prevState);
activeTrigger.current = event.currentTarget;
-
(document.querySelector(POPPER_CONTENT_SELECTOR) as HTMLElement)?.focus();
}
function closePopper() {
- setIsOpen(false);
activeTrigger.current?.focus();
+ setIsOpen(false);
+ setHighlightedItem(null);
+ setHighlightedIndex(undefined);
}
useRestrict({ condition: isOpen });
-
return (
{children}
diff --git a/components/ui/primitives/popper/popper-trigger.tsx b/components/ui/primitives/popper/popper-trigger.tsx
index 6b1f5b9..8cc02ce 100644
--- a/components/ui/primitives/popper/popper-trigger.tsx
+++ b/components/ui/primitives/popper/popper-trigger.tsx
@@ -3,9 +3,10 @@
import { PopperTriggerProps } from '@/components/ui/primitives/popper/popper.types';
import { usePopper } from '@/components/ui/primitives/popper/popper-context';
import { Button } from '@/components/ui/button';
+import { chain } from '@/utils/chain';
export function PopperTrigger(props: PopperTriggerProps) {
- const { children } = props;
+ const { children, onMouseDown, onClick, ...etc } = props;
const { openPopper, id } = usePopper();
function handleMouseDown(event: React.MouseEvent) {
@@ -13,7 +14,13 @@ export function PopperTrigger(props: PopperTriggerProps) {
}
return (
-