Skip to content

Commit 8a6bbd2

Browse files
committed
fix!: Flyout for Meganav
1 parent 9cadfc8 commit 8a6bbd2

File tree

4 files changed

+297
-137
lines changed

4 files changed

+297
-137
lines changed

src/core/Flyout.tsx

Lines changed: 92 additions & 50 deletions
Original file line numberDiff line numberDiff line change
@@ -9,76 +9,118 @@ import {
99
NavigationMenuLink,
1010
} from "@radix-ui/react-navigation-menu";
1111
import cn from "./utils/cn";
12+
import { createPortal } from "react-dom";
1213

13-
const NavigationMenuDemo = ({
14-
menuItems,
15-
navMenuStyling,
16-
flyOutStyling,
17-
}: {
14+
/**
15+
* Props for the Flyout component.
16+
*/
17+
type FlyoutProps = {
18+
/**
19+
* Array of menu items to be displayed in the flyout.
20+
*/
1821
menuItems: {
22+
/**
23+
* Label for the menu item.
24+
*/
1925
label: string;
26+
/**
27+
* Optional content to be displayed in the flyout panel.
28+
*/
2029
content?: React.ReactNode;
30+
/**
31+
* Optional link for the menu item.
32+
*/
2133
link?: string;
34+
/**
35+
* Optional styling for the flyout panel.
36+
*/
2237
panelStyling?: string;
2338
}[];
24-
navMenuStyling?: string;
25-
flyOutStyling?: string;
26-
}) => {
27-
const menuLinkStyles =
28-
"ui-text-menu3 text-neutral-1000 font-bold px-12 py-8 hover:bg-neutral-100 hover:text-neutral-1300 cursor-pointer flex items-center";
29-
const viewPortStyling =
30-
"relative overflow-hidden w-full h-[var(--radix-navigation-menu-viewport-height)] origin-[top_center] transition-[width,_height] duration-300 data-[state=closed]:animate-scaleOut data-[state=open]:animate-scaleIn sm:w-[var(--radix-navigation-menu-viewport-width)]";
31-
const boxShadowViewport =
32-
"shadow-[0_201px_56px_0_rgba(20,25,36,0),0_129px_51px_0_rgba(20,25,36,0.02),0_72px_43px_0_rgba(20,25,36,0.06),0_32px_32px_0_rgba(20,25,36,0.10),0_8px_18px_0_rgba(20,25,36,0.12)]";
39+
/**
40+
* Optional class name for the flyout container.
41+
*/
42+
className?: string;
43+
/**
44+
* Optional class name for the flyout element.
45+
*/
46+
flyOutClassName?: string;
47+
/**
48+
* Optional class name for the menu link.
49+
*/
50+
menuLinkClassName?: string;
51+
/**
52+
* Optional class name for the viewport.
53+
*/
54+
viewPortClassName?: string;
55+
/**
56+
* Flag to indicate if animation should be applied.
57+
*/
58+
hasAnimation: boolean;
59+
};
3360

61+
const DEFAULT_MENU_LINK_STYLING =
62+
"ui-text-menu3 font-bold text-neutral-1000 dark:neutral-300 hover:bg-neutral-100 dark:hover:bg-neutral-1200 hover:text-neutral-1300 dark:hover:text-neutral-000 px-12 py-8 flex items-center justify-between";
63+
const DEFAULT_VIEWPORT_STYLING =
64+
"relative overflow-hidden w-full h-[var(--radix-navigation-menu-viewport-height)] origin-[top_center] transition-[width,_height] duration-300 data-[state=closed]:animate-scale-out data-[state=open]:animate-scale-in sm:w-[var(--radix-navigation-menu-viewport-width)]";
65+
const PANEL_ANIMATION =
66+
"data-[motion=from-end]:animate-enter-from-right data-[motion=from-start]:animate-enter-from-left data-[motion=to-end]:animate-exit-to-right data-[motion=to-start]:animate-exit-to-left";
67+
68+
const Flyout = ({
69+
menuItems,
70+
className,
71+
flyOutClassName,
72+
menuLinkClassName,
73+
viewPortClassName,
74+
hasAnimation,
75+
}: FlyoutProps) => {
3476
return (
35-
<NavigationMenu className={cn(navMenuStyling, "flex w-full relative")}>
77+
<NavigationMenu className={cn(className, "flex w-full relative")}>
3678
<NavigationMenuList className="flex list-none center">
37-
{menuItems.map((menuItem) => (
38-
<NavigationMenuItem key={menuItem.label}>
39-
{menuItem.content ? (
40-
<>
41-
<NavigationMenuTrigger
42-
className={cn(
43-
menuLinkStyles,
44-
"group outline-none focus:outline-none select-none flex items-center justify-between",
45-
)}
46-
>
47-
{menuItem.label}
48-
</NavigationMenuTrigger>
79+
{menuItems.map(({ label, content, link, panelStyling }) =>
80+
content ? (
81+
<NavigationMenuItem key={label}>
82+
<NavigationMenuTrigger
83+
className={cn(
84+
"group outline-none focus:outline-none select-none cursor-pointer",
85+
DEFAULT_MENU_LINK_STYLING,
86+
menuLinkClassName,
87+
)}
88+
>
89+
{label}
90+
</NavigationMenuTrigger>
91+
{createPortal(
4992
<NavigationMenuContent
5093
className={cn(
51-
menuItem.panelStyling,
52-
"absolute left-0 top-0 p-24 z-10",
94+
"absolute inset-x-0 top-0 p-24 z-10",
95+
hasAnimation && PANEL_ANIMATION,
96+
panelStyling,
5397
)}
5498
>
55-
{menuItem.content}
56-
</NavigationMenuContent>
57-
</>
58-
) : (
59-
<NavigationMenuLink>
60-
<a href={menuItem.link} className={menuLinkStyles}>
61-
{menuItem.label}
62-
</a>
63-
</NavigationMenuLink>
64-
)}
65-
</NavigationMenuItem>
66-
))}
99+
{content}
100+
</NavigationMenuContent>,
101+
document.body,
102+
)}
103+
</NavigationMenuItem>
104+
) : (
105+
<NavigationMenuLink key={label}>
106+
<a
107+
href={link}
108+
className={cn(DEFAULT_MENU_LINK_STYLING, menuLinkClassName)}
109+
>
110+
{label}
111+
</a>
112+
</NavigationMenuLink>
113+
),
114+
)}
67115
</NavigationMenuList>
68116

69-
<div
70-
className={cn("absolute left-0 top-full flex w-full", flyOutStyling)}
71-
>
117+
<div className={cn("absolute left-0 top-full", flyOutClassName)}>
72118
<NavigationMenuViewport
73-
className={cn(
74-
viewPortStyling,
75-
boxShadowViewport,
76-
"border border-neutral-000 rounded-2xl mt-8",
77-
)}
119+
className={cn(DEFAULT_VIEWPORT_STYLING, viewPortClassName)}
78120
/>
79121
</div>
80122
</NavigationMenu>
81123
);
82124
};
83125

84-
export default NavigationMenuDemo;
126+
export default Flyout;

src/core/Flyout/Flyout.stories.tsx

Lines changed: 84 additions & 54 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,4 @@
11
import React from "react";
2-
import { StoryFn } from "@storybook/react";
32
import Flyout from "../Flyout";
43
import ProductTile from "../ProductTile";
54
import FeaturedLink from "../FeaturedLink";
@@ -11,8 +10,6 @@ export default {
1110
tags: ["autodocs"],
1211
};
1312

14-
const Template: StoryFn = (args) => <div className="h-[450px]"><Flyout {...args} /></div>;
15-
1613
const platforms = [
1714
"Infrastructure",
1815
"Integrations",
@@ -28,7 +25,6 @@ const ProductsGrid = () => {
2825
<div className="grid grid-cols-2">
2926
{Object.keys(products).map((product) => (
3027
<ProductTile
31-
className="bg-neutral-000"
3228
name={product as ProductName}
3329
key={product}
3430
selected={selectedProduct === product}
@@ -49,11 +45,13 @@ const Panels = ({
4945
<div className="flex flex-row gap-x-24">
5046
<div className="flex-6">{panelLeft}</div>
5147
<div className="flex-4 pt-12">
52-
<p className="ui-text-overline2 text-neutral-700 pb-6">platform</p>
48+
<p className="ui-text-overline2 text-neutral-700 dark:text-neutral-600 pb-6">
49+
platform
50+
</p>
5351
{platforms.map((item) => (
5452
<li className="list-none py-6" key={item}>
5553
<a
56-
className="ui-text-menu3 text-neutral-1000"
54+
className="ui-text-menu3 text-neutral-1000 dark:text-neutral-300"
5755
href={`/platform/${item.toLowerCase()}`}
5856
>
5957
{item}
@@ -65,63 +63,95 @@ const Panels = ({
6563
);
6664

6765
const DefaultPanelLeft = ({ title, desc }: { title: string; desc: string }) => (
68-
<div className="bg-neutral-100 w-full p-24">
69-
<h4 className="ui-text-h4">{title}</h4>
70-
<p className="ui-text-p3 text-neutral-800 mt-8">{desc}</p>
66+
<div className="bg-neutral-100 dark:bg-neutral-1200 w-full p-24">
67+
<h4 className="ui-text-h4 text-neutral-1300 dark:text-neutral-000">
68+
{title}
69+
</h4>
70+
<p className="ui-text-p3 text-neutral-800 dark:text-neutral-500 mt-8">
71+
{desc}
72+
</p>
7173
<FeaturedLink
7274
url=""
73-
additionalCSS="text-neutral-1300"
75+
additionalCSS="text-neutral-1300 dark:text-neutral-000"
7476
iconColor="text-orange-600"
7577
>
7678
Learn more
7779
</FeaturedLink>
7880
</div>
7981
);
8082

81-
const panelStyling = "w-full sm:w-[816px] bg-neutral-000";
83+
const panelStyling = "w-full sm:w-[816px] bg-neutral-000 dark:bg-neutral-1300";
8284

83-
export const Default = Template.bind({});
84-
Default.args = {
85-
menuItems: [
86-
{ label: "Home", content: null, link: "" },
87-
{
88-
label: "Products",
89-
content: <Panels panelLeft={<ProductsGrid />} platforms={platforms} />,
90-
panelStyling: panelStyling,
91-
},
92-
{
93-
label: "Solutions",
94-
content: (
95-
<Panels
96-
panelLeft={
97-
<DefaultPanelLeft
98-
title="Fan engagement"
99-
desc=" Capture the attention of millions of fans during live events."
100-
/>
101-
}
102-
platforms={platforms}
103-
/>
104-
),
105-
panelStyling: panelStyling,
106-
},
107-
{
108-
label: "Company",
109-
content: (
110-
<Panels
111-
panelLeft={
112-
<DefaultPanelLeft
113-
title="Leading the realtime revolution"
114-
desc="Hear from our founders about Ably’s ambitious plans to become the world’s definitive realtime platform."
115-
/>
116-
}
117-
platforms={platforms}
118-
/>
119-
),
120-
panelStyling: panelStyling,
85+
const menuItems = [
86+
{ label: "Home", content: null, link: "" },
87+
{
88+
label: "Products",
89+
content: <Panels panelLeft={<ProductsGrid />} platforms={platforms} />,
90+
panelStyling: panelStyling,
91+
},
92+
{
93+
label: "Solutions",
94+
content: (
95+
<Panels
96+
panelLeft={
97+
<DefaultPanelLeft
98+
title="Fan engagement"
99+
desc=" Capture the attention of millions of fans during live events."
100+
/>
101+
}
102+
platforms={platforms}
103+
/>
104+
),
105+
panelStyling: panelStyling,
106+
},
107+
{
108+
label: "Company",
109+
content: (
110+
<Panels
111+
panelLeft={
112+
<DefaultPanelLeft
113+
title="Leading the realtime revolution"
114+
desc="Hear from our founders about Ably’s ambitious plans to become the world’s definitive realtime platform."
115+
/>
116+
}
117+
platforms={platforms}
118+
/>
119+
),
120+
panelStyling: panelStyling,
121+
},
122+
{ label: "Pricing", content: null, link: "/pricing" },
123+
{ label: "Docs", content: null, link: "/docs" },
124+
];
125+
126+
const flyOutDefaultArgs = {
127+
menuItems: menuItems,
128+
className: "justify-center",
129+
flyOutClassName: "flex w-full justify-center",
130+
viewPortClassName:
131+
"ui-shadow-lg-medium border border-neutral-000 dark:border-neutral-1300 rounded-2xl mt-8",
132+
hasAnimation: true,
133+
};
134+
135+
export const Default = {
136+
render: () => (
137+
<div className="h-[450px]">
138+
<Flyout {...flyOutDefaultArgs} />
139+
</div>
140+
),
141+
};
142+
143+
export const StandardContainer = {
144+
render: () => (
145+
<div className="ui-standard-container h-[450px] bg-neutral-000">
146+
<Flyout {...flyOutDefaultArgs} />
147+
</div>
148+
),
149+
parameters: {
150+
docs: {
151+
description: {
152+
story:
153+
"The Flyout component is positioned within a standard container and Animation is enabled",
154+
},
121155
},
122-
{ label: "Pricing", content: null, link: "/pricing" },
123-
{ label: "Docs", content: null, link: "/docs" },
124-
],
125-
navMenuStyling: "justify-center",
126-
flyOutStyling: "justify-center",
156+
},
127157
};

0 commit comments

Comments
 (0)