From fe3af52a5435bd9ea37646d797f1b3305250725a Mon Sep 17 00:00:00 2001 From: Brian Richter Date: Thu, 30 Jan 2025 11:28:57 -0800 Subject: [PATCH] feat(c): dropdown direction added --- .gitignore | 1 + src/components/checkbox/Checkbox.tsx | 29 +++++-- src/components/dropdown/Dropdown.tsx | 97 ++++++++++++++++++---- src/components/label/Label.tsx | 2 +- src/components/navigation/NavMenu.tsx | 78 ++++++----------- src/docs/App.tsx | 32 ++++++- src/docs/pages/components/DropdownPage.tsx | 28 +++++++ src/docs/pages/components/NavMenuPage.tsx | 49 +++++++++++ 8 files changed, 235 insertions(+), 81 deletions(-) diff --git a/.gitignore b/.gitignore index de4d1f0..5f6c423 100644 --- a/.gitignore +++ b/.gitignore @@ -1,2 +1,3 @@ dist node_modules +tmp \ No newline at end of file diff --git a/src/components/checkbox/Checkbox.tsx b/src/components/checkbox/Checkbox.tsx index 56883fc..a69d618 100644 --- a/src/components/checkbox/Checkbox.tsx +++ b/src/components/checkbox/Checkbox.tsx @@ -10,28 +10,39 @@ export interface CheckboxProps export const Checkbox = React.forwardRef( ({ className, label, id, ...props }, ref) => { + const checkboxId = id || React.useId(); + return ( -
-
+
+
- +
{label && ( diff --git a/src/components/dropdown/Dropdown.tsx b/src/components/dropdown/Dropdown.tsx index 0f76f39..ef2b23d 100644 --- a/src/components/dropdown/Dropdown.tsx +++ b/src/components/dropdown/Dropdown.tsx @@ -1,4 +1,5 @@ import * as React from "react"; +import { createPortal } from "react-dom"; import { cn } from "../../utils/cn"; import { ZINDEX } from "../../utils/z-index"; @@ -6,6 +7,8 @@ export interface DropdownProps { trigger: React.ReactNode; children: React.ReactNode; align?: "left" | "right"; + direction?: "down" | "up"; + variant?: "default" | "nav"; className?: string; } @@ -13,10 +16,13 @@ export const Dropdown: React.FC = ({ trigger, children, align = "left", + direction = "down", + variant = "default", className, }) => { const [isOpen, setIsOpen] = React.useState(false); const dropdownRef = React.useRef(null); + const triggerRef = React.useRef(null); React.useEffect(() => { const handleClickOutside = (event: MouseEvent) => { @@ -25,30 +31,87 @@ export const Dropdown: React.FC = ({ } }; + const handleEscape = (event: KeyboardEvent) => { + if (event.key === 'Escape') { + setIsOpen(false); + } + }; + document.addEventListener("mousedown", handleClickOutside); - return () => document.removeEventListener("mousedown", handleClickOutside); - }, []); + if (variant === "nav") { + document.addEventListener("keydown", handleEscape); + } + + return () => { + document.removeEventListener("mousedown", handleClickOutside); + if (variant === "nav") { + document.removeEventListener("keydown", handleEscape); + } + }; + }, [variant]); + + const triggerClasses = cn({ + "cursor-pointer": true, + "flex items-center gap-1 text-sm font-medium text-gray-600 hover:text-gray-900 dark:text-gray-400 dark:hover:text-gray-200 transition-colors": + variant === "nav", + }); + + const menuClasses = cn( + "absolute rounded-lg shadow-lg min-w-[8rem] py-1", + variant === "default" && [ + "bg-white dark:bg-gray-900", + "border border-gray-200 dark:border-gray-800", + ], + variant === "nav" && [ + "bg-white dark:bg-gray-800", + "border border-gray-200 dark:border-gray-700", + "w-48 rounded-md", + ], + { + "left-0": align === "left", + "right-0": align === "right", + "top-full mt-2": direction === "down", + "bottom-full mb-2": direction === "up", + }, + className + ); + + // Get position for the dropdown menu + const getDropdownPosition = () => { + if (!triggerRef.current) return {}; + const rect = triggerRef.current.getBoundingClientRect(); + + return { + position: 'fixed' as const, + left: rect.left, + right: window.innerWidth - rect.right, + ...(direction === 'down' + ? { top: rect.bottom + 8 } + : { bottom: window.innerHeight - rect.top + 8 } + ), + }; + }; return (
-
setIsOpen(!isOpen)}>{trigger}
- {isOpen && ( +
setIsOpen(!isOpen)} + className={triggerClasses} + > + {trigger} +
+ {isOpen && createPortal(
{children} -
+
, + document.body )}
); diff --git a/src/components/label/Label.tsx b/src/components/label/Label.tsx index bdcb8d8..d6b1e30 100644 --- a/src/components/label/Label.tsx +++ b/src/components/label/Label.tsx @@ -8,7 +8,7 @@ export const Label = React.forwardRef(
diff --git a/src/docs/pages/components/NavMenuPage.tsx b/src/docs/pages/components/NavMenuPage.tsx index 744e4aa..efc4026 100644 --- a/src/docs/pages/components/NavMenuPage.tsx +++ b/src/docs/pages/components/NavMenuPage.tsx @@ -20,6 +20,12 @@ export const NavMenuPage = () => { required: true, description: "Menu items", }, + { + name: "direction", + type: "'down' | 'up'", + defaultValue: "down", + description: "Direction for the menu to expand", + }, { name: "className", type: "string", @@ -120,6 +126,49 @@ const MyComponent = () => ( + + + Option 1 + Option 2 + Option 3 + + + + Option 1 + Option 2 + Option 3 +`} + > +
+
+ + Option 1 + Option 2 + Option 3 + +
+ + Option 1 + Option 2 + Option 3 + +
+