diff --git a/eslint.config.js b/eslint.config.js index 57d26bd3..87bcec24 100644 --- a/eslint.config.js +++ b/eslint.config.js @@ -21,6 +21,7 @@ export default tseslint.config( globals: globals.browser, }, rules: { + "@typescript-eslint/no-empty-object-type": "off", "@typescript-eslint/no-unused-vars": "warn", "@typescript-eslint/no-non-null-assertion": "warn", "@typescript-eslint/no-explicit-any": "warn", diff --git a/package.json b/package.json index 3d9e536d..16428a0b 100644 --- a/package.json +++ b/package.json @@ -12,6 +12,7 @@ "dev": "pnpm storybook", "test": "test-storybook", "build": "tsc && vite build", + "build:watch": "tsc && vite build --watch", "lint": "eslint . --report-unused-disable-directives --max-warnings 0", "format": "prettier --write --ignore-unknown .", "preview": "vite", diff --git a/src/primitives/Navbar/Navbar.mdx b/src/components/Navbar/Navbar.mdx similarity index 97% rename from src/primitives/Navbar/Navbar.mdx rename to src/components/Navbar/Navbar.mdx index 93ce3890..e3d5aeef 100644 --- a/src/primitives/Navbar/Navbar.mdx +++ b/src/components/Navbar/Navbar.mdx @@ -1,4 +1,5 @@ import { Meta, Title, Primary, Stories, Controls, Story } from "@storybook/blocks"; + import { Navbar } from "./Navbar"; import * as NavbarStories from "./Navbar.stories"; @@ -20,4 +21,4 @@ The Navbar component displays a navigation bar with optional logos and text. It # Other variations - + diff --git a/src/primitives/Navbar/Navbar.stories.tsx b/src/components/Navbar/Navbar.stories.tsx similarity index 98% rename from src/primitives/Navbar/Navbar.stories.tsx rename to src/components/Navbar/Navbar.stories.tsx index ef684d64..64442818 100644 --- a/src/primitives/Navbar/Navbar.stories.tsx +++ b/src/components/Navbar/Navbar.stories.tsx @@ -9,7 +9,7 @@ import { Navbar } from "./Navbar"; // Adjust the path as needed const meta = { - title: "Primitives/Navbar", + title: "Components/Navbar", component: Navbar, args: { text: { diff --git a/src/components/Navbar/Navbar.tsx b/src/components/Navbar/Navbar.tsx new file mode 100644 index 00000000..ce03b0b1 --- /dev/null +++ b/src/components/Navbar/Navbar.tsx @@ -0,0 +1,46 @@ +"use client"; + +import * as React from "react"; + +import { NavbarGeneric } from "./components/NavbarGeneric"; +import { NavbarLogo, NavbarLogoProps } from "./components/NavbarLogo"; +import { + NavbarCenterSection, + NavbarEndSection, + NavbarStartSection, +} from "./components/NavbarSections"; +import { NavbarSeparator } from "./components/NavbarSeparator"; +import { NavbarTitle, NavbarTitleProps } from "./components/NavbarTitle"; + +export interface NavbarProps { + className?: string; + primaryLogo?: NavbarLogoProps; + secondaryLogo?: NavbarLogoProps; + showDivider?: boolean; + text?: NavbarTitleProps; + children?: React.ReactNode; +} + +export const Navbar = ({ + className, + primaryLogo, + secondaryLogo, + showDivider = true, + text, + children, +}: NavbarProps) => { + return ( + + + + {showDivider && } +
+ {secondaryLogo && } + {text && } +
+
+ {"hola"} + {children} +
+ ); +}; diff --git a/src/components/Navbar/components/NavbarGeneric.mdx b/src/components/Navbar/components/NavbarGeneric.mdx new file mode 100644 index 00000000..03d0d720 --- /dev/null +++ b/src/components/Navbar/components/NavbarGeneric.mdx @@ -0,0 +1,89 @@ +import { Meta, Story, Canvas, Controls } from "@storybook/blocks"; + +import * as NavbarGenericStories from "./NavbarGeneric.stories"; + + + +# NavbarGeneric + +A flexible navbar component that handles layout and positioning of navbar sections. + +Key features: + +- Section order doesn't matter - they'll always render in Start, Center, End order +- Sections can be omitted - empty sections will be created automatically +- Configurable behavior (static, sticky, fixed) +- Optional backdrop blur effect +- Optional shadow effect + +## Usage + +```tsx +import { + NavbarGeneric, + NavbarStartSection, + NavbarCenterSection, + NavbarEndSection, +} from "@/components/Navbar"; + +export const MyNavbar = () => ( + + + + + + + + + + + +); +``` + +```tsx +// Example with all props +import { NavbarGeneric } from "@/components/Navbar"; + +export const MyNavbar = () => ( + + {/* Your navbar content */} + +); +``` + +```tsx +import { + NavbarGeneric, + NavbarStartSection, + NavbarCenterSection, + NavbarEndSection, +} from "@/components/Navbar"; + +export const MyNavbar = () => ( + + {/* Order doesn't matter */} + End + Start + + {/* Center section can be omitted */} + +); +``` + +## Props + +The NavbarGeneric component accepts the following props: + +- `behavior` - Controls the positioning behavior of the navbar + - `static` - Normal document flow + - `sticky` - Sticks to the top when scrolling (default) + - `fixed` - Fixed to the viewport +- `blur` - Enables backdrop blur effect (default: true) +- `shadow` - Adds a subtle shadow below the navbar (default: true) + + + +### Default + + diff --git a/src/components/Navbar/components/NavbarGeneric.stories.tsx b/src/components/Navbar/components/NavbarGeneric.stories.tsx new file mode 100644 index 00000000..f40efb83 --- /dev/null +++ b/src/components/Navbar/components/NavbarGeneric.stories.tsx @@ -0,0 +1,126 @@ +import * as React from "react"; + +import type { Meta, StoryObj } from "@storybook/react"; + +import { NavbarGeneric } from "./NavbarGeneric"; +import { NavbarCenterSection, NavbarEndSection, NavbarStartSection } from "./NavbarSections"; + +const meta = { + title: "Components/NavbarGeneric", + component: NavbarGeneric, + argTypes: { + behavior: { + control: "radio", + options: ["static", "sticky", "fixed"], + description: "Behavior of the navbar", + table: { + defaultValue: { summary: "sticky" }, + }, + }, + }, +} satisfies Meta; + +export default meta; + +type Story = StoryObj; + +const DefaultContent = () => ( + <> + +
Start Content
+
+ +
Center Content
+
+ +
End Content
+
+ +); + +const ScrollDecorator = (Story: React.ComponentType) => ( +
+ +
Scroll content
+
+); + +export const Default: Story = { + args: { + children: , + }, +}; + +export const Static: Story = { + args: { + behavior: "static", + children: , + }, + argTypes: { + children: { + table: { + disable: true, + }, + }, + }, + decorators: [ScrollDecorator], +}; + +export const Sticky: Story = { + args: { + behavior: "sticky", + children: , + }, + argTypes: { + children: { + table: { + disable: true, + }, + }, + }, + decorators: [ScrollDecorator], +}; + +export const Fixed: Story = { + args: { + behavior: "fixed", + children: , + }, + argTypes: { + children: { + table: { + disable: true, + }, + }, + }, + decorators: [ScrollDecorator], +}; + +export const RandomOrder: Story = { + render: (args) => ( + + +
End First
+
+ +
Start Second
+
+ +
Center Last
+
+
+ ), +}; + +export const OmittedSections: Story = { + render: (args) => ( + + +
Only Start
+
+ +
And End
+
+
+ ), +}; diff --git a/src/components/Navbar/components/NavbarGeneric.tsx b/src/components/Navbar/components/NavbarGeneric.tsx new file mode 100644 index 00000000..73869999 --- /dev/null +++ b/src/components/Navbar/components/NavbarGeneric.tsx @@ -0,0 +1,65 @@ +"use client"; + +import * as React from "react"; + +import { tv, type VariantProps } from "tailwind-variants"; + +import { cn } from "@/lib"; + +import { filterAndSortChildren } from "../utils/filterAndSortChildren"; + +const navbarVariants = tv({ + base: "z-50 flex h-16 w-full items-center justify-between bg-white/5 px-20 py-2.5", + variants: { + behavior: { + static: "", + sticky: "sticky top-0", + fixed: "fixed top-0", + }, + blur: { + true: "backdrop-blur-[22px]", + false: "", + }, + shadow: { + true: "shadow-[0px_4px_24px_0px_rgba(0,0,0,0.08)]", + false: "", + }, + }, + defaultVariants: { + behavior: "sticky", + blur: true, + shadow: true, + }, +}); + +export interface NavbarGenericProps + extends React.HTMLAttributes, + VariantProps { + /** Children should be NavbarStartSection, NavbarCenterSection, or NavbarEndSection components */ + children?: React.ReactNode; + /** Position of the navbar: static, sticky, or fixed + * @default sticky + */ + behavior?: "static" | "sticky" | "fixed"; + /** Enables a backdrop blur effect behind the navbar. Useful for transparent or semi-transparent navbars. + * @default true + */ + blur?: boolean; + /** Adds a subtle shadow below the navbar to create depth and separation from the content. + * @default true + */ + shadow?: boolean; +} + +export const NavbarGeneric = React.forwardRef( + ({ className, behavior, children, blur, shadow, ...props }, ref) => { + const navbarStyle = navbarVariants({ behavior, blur, shadow }); + const filteredChildren = filterAndSortChildren(children); + return ( +
+ {filteredChildren} +
+ ); + }, +); +NavbarGeneric.displayName = "NavbarGeneric"; diff --git a/src/primitives/Navbar/components/NavbarLogo.tsx b/src/components/Navbar/components/NavbarLogo.tsx similarity index 98% rename from src/primitives/Navbar/components/NavbarLogo.tsx rename to src/components/Navbar/components/NavbarLogo.tsx index 521a284b..84874e14 100644 --- a/src/primitives/Navbar/components/NavbarLogo.tsx +++ b/src/components/Navbar/components/NavbarLogo.tsx @@ -1,5 +1,3 @@ -"use client"; - import { GitcoinLogo } from "@/assets/logos/gitcoin"; const DefaultLogo = GitcoinLogo; diff --git a/src/components/Navbar/components/NavbarSections.tsx b/src/components/Navbar/components/NavbarSections.tsx new file mode 100644 index 00000000..892af763 --- /dev/null +++ b/src/components/Navbar/components/NavbarSections.tsx @@ -0,0 +1,40 @@ +import * as React from "react"; + +import { tv } from "tailwind-variants"; + +import { cn } from "@/lib"; + +const navbarSectionStyle = tv({ + base: "flex items-center gap-4", + variants: { + section: { + start: "", + center: "hidden md:block", + end: "", + }, + }, +}); + +export const NavbarStartSection = React.forwardRef< + HTMLDivElement, + React.HTMLAttributes +>(({ className, ...props }, ref) => ( +
+)); +NavbarStartSection.displayName = "NavbarStartSection"; + +export const NavbarCenterSection = React.forwardRef< + HTMLDivElement, + React.HTMLAttributes +>(({ className, ...props }, ref) => ( +
+)); +NavbarCenterSection.displayName = "NavbarCenterSection"; + +export const NavbarEndSection = React.forwardRef< + HTMLDivElement, + React.HTMLAttributes +>(({ className, ...props }, ref) => ( +
+)); +NavbarEndSection.displayName = "NavbarEndSection"; diff --git a/src/components/Navbar/components/NavbarSeparator.tsx b/src/components/Navbar/components/NavbarSeparator.tsx new file mode 100644 index 00000000..9e78179c --- /dev/null +++ b/src/components/Navbar/components/NavbarSeparator.tsx @@ -0,0 +1,18 @@ +import * as React from "react"; + +import { tv } from "tailwind-variants"; + +import { cn } from "@/lib"; + +const separatorStyle = tv({ + base: "h-4 w-0 border-1.5 border-grey-900 bg-grey-900", +}); + +const NavbarSeparator = React.forwardRef>( + ({ className, ...props }, ref) => ( +
+ ), +); +NavbarSeparator.displayName = "NavbarSeparator"; + +export { NavbarSeparator }; diff --git a/src/primitives/Navbar/components/NavbarTitle.tsx b/src/components/Navbar/components/NavbarTitle.tsx similarity index 95% rename from src/primitives/Navbar/components/NavbarTitle.tsx rename to src/components/Navbar/components/NavbarTitle.tsx index c6de7e01..e889175d 100644 --- a/src/primitives/Navbar/components/NavbarTitle.tsx +++ b/src/components/Navbar/components/NavbarTitle.tsx @@ -1,5 +1,3 @@ -"use client"; - import { cn } from "@/lib"; export interface NavbarTitleProps { diff --git a/src/components/Navbar/index.ts b/src/components/Navbar/index.ts new file mode 100644 index 00000000..3a6b81d1 --- /dev/null +++ b/src/components/Navbar/index.ts @@ -0,0 +1,6 @@ +export * from "./Navbar"; +export * from "./components/NavbarGeneric"; +export * from "./components/NavbarSections"; +export * from "./components/NavbarSeparator"; +export * from "./components/NavbarLogo"; +export * from "./components/NavbarTitle"; diff --git a/src/components/Navbar/utils/filterAndSortChildren.tsx b/src/components/Navbar/utils/filterAndSortChildren.tsx new file mode 100644 index 00000000..5c82dcc2 --- /dev/null +++ b/src/components/Navbar/utils/filterAndSortChildren.tsx @@ -0,0 +1,50 @@ +import * as React from "react"; + +import { + NavbarCenterSection, + NavbarEndSection, + NavbarStartSection, +} from "../components/NavbarSections"; + +const sectionComponentsOrder = [NavbarStartSection, NavbarCenterSection, NavbarEndSection] as const; +type NavbarSection = (typeof sectionComponentsOrder)[number]; + +export const filterAndSortChildren = (children: React.ReactNode) => { + const validDisplayNames = sectionComponentsOrder.map((comp) => comp.displayName) as string[]; + const finalChildren = new Array(sectionComponentsOrder.length).fill(null); + + // Handle the case that children is a React.Fragment + const processChild = (child: React.ReactNode) => { + if (React.isValidElement(child)) { + const mightBeFragment = + React.isValidElement(child) && + typeof child.type === "function" && + !(child.type as any)?.displayName; + + if (mightBeFragment) { + const rendered = (child.type as React.FC)(child.props); + if (React.isValidElement(rendered)) { + React.Children.forEach(rendered.props.children, processChild); + } + } else { + const displayName = (child.type as NavbarSection).displayName; + if (displayName) { + const index = validDisplayNames.indexOf(displayName); + if (index !== -1) { + finalChildren[index] = child; + } + } + } + } + }; + + React.Children.forEach(children, processChild); + + return finalChildren.map((child, i) => { + if (child === null) { + const SectionComponent = sectionComponentsOrder[i]; + return ; + } + return child; + }); +}; diff --git a/src/index.ts b/src/index.ts index 6356bc2e..b6c395ad 100644 --- a/src/index.ts +++ b/src/index.ts @@ -10,7 +10,6 @@ export * from "./primitives/Icon"; export * from "./primitives/Indicators"; export * from "./primitives/ListGrid"; export * from "./primitives/Modal"; -export * from "./primitives/Navbar"; export * from "./primitives/ProgressBar"; export * from "./primitives/RadioGroup"; export * from "./primitives/ScrollArea"; @@ -27,6 +26,8 @@ export * from "./primitives/Checkbox"; export * from "./components/CreateButton"; export * from "./components/IconLabel"; +export * from "./components/Navbar"; +export * from "./components/ProgressModal"; export * from "./components/RadioGroupList"; export * from "./components/SideNav"; export * from "./components/Toaster"; diff --git a/src/primitives/Navbar/Navbar.tsx b/src/primitives/Navbar/Navbar.tsx deleted file mode 100644 index 4f93e2a8..00000000 --- a/src/primitives/Navbar/Navbar.tsx +++ /dev/null @@ -1,55 +0,0 @@ -"use client"; - -import * as React from "react"; - -import { tv } from "tailwind-variants"; - -import { cn } from "@/lib"; - -import { NavbarLogo, NavbarLogoProps, NavbarTitle, NavbarTitleProps } from "./components"; - -const navbarVariants = tv({ - slots: { - base: "top-0 flex h-[64px] w-full shrink-0 flex-col items-center justify-center gap-2 p-[10px_80px]", - container: "flex w-full items-center justify-between", - leftSection: "flex items-center gap-4", - divider: "h-4 border-r-1.5 border-grey-900", - rightSection: "flex items-center", - }, -}); - -export interface NavbarProps { - className?: string; - primaryLogo?: NavbarLogoProps; - secondaryLogo?: NavbarLogoProps; - showDivider?: boolean; - text?: NavbarTitleProps; - children?: React.ReactNode; -} - -export const Navbar = ({ - className, - primaryLogo, - secondaryLogo, - showDivider = true, - text, - children, -}: NavbarProps) => { - const { base, container, leftSection, divider, rightSection } = navbarVariants(); - - return ( - - ); -}; diff --git a/src/primitives/Navbar/components/index.ts b/src/primitives/Navbar/components/index.ts deleted file mode 100644 index f5c377a9..00000000 --- a/src/primitives/Navbar/components/index.ts +++ /dev/null @@ -1,2 +0,0 @@ -export * from "./NavbarLogo"; -export * from "./NavbarTitle"; diff --git a/src/primitives/Navbar/index.ts b/src/primitives/Navbar/index.ts deleted file mode 100644 index 21348000..00000000 --- a/src/primitives/Navbar/index.ts +++ /dev/null @@ -1 +0,0 @@ -export * from "./Navbar"; diff --git a/src/primitives/index.ts b/src/primitives/index.ts index 04f29e7a..82baa6fa 100644 --- a/src/primitives/index.ts +++ b/src/primitives/index.ts @@ -11,7 +11,6 @@ export * from "./ListGrid"; export * from "./Markdown"; export * from "./MarkdownEditor"; export * from "./Modal"; -export * from "./Navbar"; export * from "./ProgressBar"; export * from "./RadioGroup"; export * from "./ScrollArea"; diff --git a/tailwind.config.ts b/tailwind.config.ts index 292be977..c64c9c4b 100644 --- a/tailwind.config.ts +++ b/tailwind.config.ts @@ -29,6 +29,7 @@ export default withTV({ "100": "100", }, boxShadow: { + navbar: "0px 4px 24px -8px rgba(0, 0, 0, 0.08)", toast: "0px 24px 94px 0px rgba(0, 0, 0, 0.17), 0px 7.235px 28.338px 0px rgba(0, 0, 0, 0.11), 0px 3.005px 11.77px 0px rgba(0, 0, 0, 0.09), 0px 1.087px 4.257px 0px rgba(0, 0, 0, 0.06)", },