Skip to content

Commit

Permalink
✨ Improve navigation motion
Browse files Browse the repository at this point in the history
  • Loading branch information
damien-schneider committed Aug 15, 2024
1 parent 1b6012f commit 396e5a6
Show file tree
Hide file tree
Showing 3 changed files with 151 additions and 20 deletions.
47 changes: 35 additions & 12 deletions src/components/full-component.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@ import {
} from "../ui/shadcn/resizable";
import { ScrollArea, ScrollBar } from "../ui/shadcn/scrollarea";

import { NavigationMenu, NavigationMenuButton } from "../ui/navigation-menu";
import { cn } from "../utils/cn";
import BadgeList from "./badge-list";
import StepToInstall from "./component-wrapper/step-to-install";
Expand Down Expand Up @@ -114,7 +115,24 @@ export default function FullComponent({
<div className="py-1 mt-1">
<ScrollArea className=" w-full rounded-md">
<ScrollAreaScrollbar orientation="horizontal" />
<div className="flex gap-2">
<NavigationMenu
className="flex gap-0.5 mb-0.5"
transitionTime={0.3}
>
{componentList.map((variant, index) => (
<NavigationMenuButton
key={`${index}-${variant.variantName}`}
className={cn(
"w-fit p-3 inline-flex items-center justify-center",
)}
onClick={() => setSelectedVariant(index + 1)}
isActive={selectedVariant === index + 1}
>
{variant.variantName}
</NavigationMenuButton>
))}
</NavigationMenu>
{/* <div className="flex gap-2">
{componentList.map((variant, index) => (
<Button
key={`${index}-${variant.variantName}`}
Expand All @@ -127,7 +145,7 @@ export default function FullComponent({
{variant.variantName}
</Button>
))}
</div>
</div> */}
</ScrollArea>
</div>
</div>
Expand All @@ -139,22 +157,27 @@ export default function FullComponent({
"flex items-center flex-col justify-center overflow-hidden",
)}
>
<div className="flex gap-2 rounded-lg w-full mb-0.5">
<Button
variant={tab === "visual" ? "neutral" : "hover-only"}
className="w-full"
<NavigationMenu
className="w-full flex gap-0.5 mb-0.5"
transitionTime={0.3}
>
<NavigationMenuButton
className={cn(
"w-full p-3 inline-flex items-center justify-center",
)}
onClick={() => handleTabChange("visual")}
isActive={tab === "visual"}
>
Visual
</Button>
<Button
variant={tab === "code" ? "neutral" : "hover-only"}
className="w-full"
</NavigationMenuButton>
<NavigationMenuButton
className="w-full p-3 inline-flex items-center justify-center"
onClick={() => handleTabChange("code")}
isActive={tab === "code"}
>
Code
</Button>
</div>
</NavigationMenuButton>
</NavigationMenu>
{tab === "visual" ? (
isResizable ? (
<ResizablePanelGroup
Expand Down
12 changes: 4 additions & 8 deletions src/lib/component-categories.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@ import {
SquareArrowRightIcon,
TagIcon,
} from "lucide-react";
import ModernDetailedKbdVariant1 from "../app/(components)/application-ui/kbd/modern-detailed-kbd/variant1";

import DynamicSettingsVariant1 from "../app/(components)/application-ui/settings/dynamic-settings/variant1";
import { ElasticSliderVariant1 } from "../app/(components)/application-ui/sliders/elastic-slider/variant1";
import { StepWithStickyColorVariant1 } from "../app/(components)/application-ui/static-steppers/code/variant1";
Expand All @@ -31,7 +31,6 @@ import ModernSimpleQuoteVariant1 from "../app/(components)/common-ui/blockquotes
import ShinyRotatingBorderButtonVariant1 from "../app/(components)/common-ui/buttons/shiny-rotating-border-button/variant1";
import { ModernInnerShadowCardVariant1 } from "../app/(components)/common-ui/cards/modern-inner-shadow/variant1";
import Dock from "../app/(components)/common-ui/navigation/mac-dock/variant1";
import { VercelNavigationVariant1 } from "../app/(components)/common-ui/navigation/vercel-navigation/variant1";
import { ShinyGradientSkeletonVariant1 } from "../app/(components)/common-ui/skeletons/shiny-gradient/variant1";
import { CarouselCylindricalVariant1 } from "../app/(components)/marketing-ui/carousels/cylindric-3d-carousel/variant1";
import { Variant1FeatureFourImages } from "../app/(components)/marketing-ui/features/feature-four-images/variant1";
Expand All @@ -41,13 +40,10 @@ import MarqueeVariant1 from "../app/(components)/marketing-ui/testimonials/marqu
import Modern3dKbdVariant1 from "../app/(components)/application-ui/kbd/modern-3d-kbd/variant1";
import { AnimatedNumberVariant1 } from "../app/(components)/marketing-ui/statistics/animated-on-scroll/variant1";
import FollowCursorVariant1 from "../app/(components)/other/cursors/follow-cursor/variant1";
import GooglePixelVariant1 from "../app/(components)/other/mock-ups/smartphone/variant1";
import {
DotsPattern,
DotsPatternVariant1,
} from "../app/(components)/other/patterns/dots-pattern/variant1";
import { GooglePixelVariant1 } from "../app/(components)/other/mock-ups/smartphone/variant1";
import { DotsPatternVariant1 } from "../app/(components)/other/patterns/dots-pattern/variant1";
import BlurAppearVariant1 from "../app/(components)/other/transition-wrappers/blur-appear/variant1";
import type { CategoryItem, PreviewComponent } from "./types/component";
import type { CategoryItem } from "./types/component";

export const componentCategories: {
name: string;
Expand Down
112 changes: 112 additions & 0 deletions src/ui/navigation-menu.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,112 @@
"use client";
import { AnimatePresence, motion } from "framer-motion";
import React, { useEffect, type ReactNode } from "react";
import { cloneElement, isValidElement, useId, useState } from "react";
import { cn } from "../utils/cn";

interface NavigationMenuProps extends React.HTMLAttributes<HTMLDivElement> {
children: ReactNode;
transitionTime?: number; // Add the transitionTime prop
}

export const NavigationMenu: React.FC<NavigationMenuProps> = ({
children,
transitionTime = 0.2, // Default transition time
...props
}) => {
const [activeElement, setActiveElement] = useState<number | null>(null);
const [elementFocused, setElementFocused] = useState<number | null>(
activeElement ?? null,
);
const handleHoverButton = (index: number | null) => {
setElementFocused(index);
};
const handleActiveElement = (index: number | null) => {
setActiveElement(index);
};
useEffect(() => {
setElementFocused(activeElement);
}, [activeElement]);
const layoutId = useId();

return (
<nav
className="flex flex-col sm:flex-row"
onMouseLeave={() => {
handleHoverButton(activeElement);
}}
{...props}
>
{React.Children.map(children, (child, index) => {
if (isValidElement(child)) {
return cloneElement(
child as React.ReactElement<NavigationButtonProps>,
{
isFocused: elementFocused === index,
onMouseEnter: () => handleHoverButton(index),
transitionTime, // Pass transitionTime to each button
layoutId,
handleActiveElement,
index,
},
);
}
return child;
})}
</nav>
);
};

interface NavigationButtonProps
extends React.ButtonHTMLAttributes<HTMLButtonElement> {
children: ReactNode;
isFocused?: boolean;
transitionTime?: number; // Add the transitionTime prop
layoutId?: string;
handleActiveElement?: (index: number | null) => void;
isActive?: boolean;
index?: number;
}

export const NavigationMenuButton: React.FC<NavigationButtonProps> = ({
children,
isFocused,
transitionTime = 0.2, // Default transition time
layoutId,
className,
handleActiveElement,
isActive,
index,
...props
}) => {
useEffect(() => {
if (isActive && handleActiveElement) {
handleActiveElement(index ?? null);
}
}, [isActive, handleActiveElement, index]);
return (
<button
type="button"
className={cn(
"text-neutral-500 text-sm font-medium py-1 px-2 rounded relative whitespace-nowrap inline-flex w-fit group",
className,
)}
{...props}
>
{children}
<AnimatePresence>
{isFocused && (
<motion.div
className="absolute top-0 left-0 right-0 bottom-0 bg-neutral-200 dark:bg-neutral-900 rounded-md -z-10 group-active:bg-neutral-300 transition-colors group-hover:bg-neutral-200/80 dark:group-hover:bg-neutral-900/80 dark:group-active:bg-neutral-800"
initial={{ opacity: 0 }}
animate={{ opacity: 1 }}
exit={{ opacity: 0 }}
transition={{ duration: transitionTime }} // Use the transitionTime prop here
layout
layoutId={layoutId}
/>
)}
</AnimatePresence>
</button>
);
};

0 comments on commit 396e5a6

Please sign in to comment.