From 7251a187c8ec2009444d9278374f63427d83243c Mon Sep 17 00:00:00 2001 From: Huss Martinez Date: Wed, 22 Jan 2025 20:15:19 +0100 Subject: [PATCH 1/5] chore: extended tailwind theme --- tailwind.config.ts | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/tailwind.config.ts b/tailwind.config.ts index 292be97..cb2ec2f 100644 --- a/tailwind.config.ts +++ b/tailwind.config.ts @@ -10,6 +10,9 @@ export default withTV({ theme: { colors, extend: { + backgroundColor: { + navbar: "bg-white/[0.03]", + }, borderWidth: { "1.5": "1.5px", }, @@ -29,9 +32,13 @@ 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)", }, + backdropBlur: { + navbar: "22px", + }, fontFamily: { "ui-mono": ["DM Mono", "serif"], "ui-sans": ["DM Sans", "sans-serif"], From 43e9163ccd47b8d19992b03372d225d8321ab21c Mon Sep 17 00:00:00 2001 From: Huss Martinez Date: Wed, 22 Jan 2025 20:15:43 +0100 Subject: [PATCH 2/5] chore: disable no-empty-object-type eslint --- eslint.config.js | 1 + 1 file changed, 1 insertion(+) diff --git a/eslint.config.js b/eslint.config.js index 57d26bd..87bcec2 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", From 1532767e5606c7c8b69fd84017853b256acfd096 Mon Sep 17 00:00:00 2001 From: Huss Martinez Date: Wed, 22 Jan 2025 20:16:21 +0100 Subject: [PATCH 3/5] feat: refactored Navbar by implementing NavbarGeneric --- package.json | 1 + .../Navbar/Navbar.mdx | 3 +- .../Navbar/Navbar.stories.tsx | 2 +- src/components/Navbar/Navbar.tsx | 46 ++++++ .../Navbar/components/NavbarGeneric.mdx | 67 +++++++++ .../components/NavbarGeneric.stories.tsx | 134 ++++++++++++++++++ .../Navbar/components/NavbarGeneric.tsx | 51 +++++++ .../Navbar/components/NavbarLogo.tsx | 2 - .../Navbar/components/NavbarSections.tsx | 40 ++++++ .../Navbar/components/NavbarSeparator.tsx | 18 +++ .../Navbar/components/NavbarTitle.tsx | 2 - src/components/Navbar/index.ts | 6 + .../Navbar/utils/filterAndSortChildren.tsx | 50 +++++++ src/index.ts | 3 +- src/primitives/Navbar/Navbar.tsx | 55 ------- src/primitives/Navbar/components/index.ts | 2 - src/primitives/Navbar/index.ts | 1 - src/primitives/index.ts | 1 - 18 files changed, 418 insertions(+), 66 deletions(-) rename src/{primitives => components}/Navbar/Navbar.mdx (97%) rename src/{primitives => components}/Navbar/Navbar.stories.tsx (98%) create mode 100644 src/components/Navbar/Navbar.tsx create mode 100644 src/components/Navbar/components/NavbarGeneric.mdx create mode 100644 src/components/Navbar/components/NavbarGeneric.stories.tsx create mode 100644 src/components/Navbar/components/NavbarGeneric.tsx rename src/{primitives => components}/Navbar/components/NavbarLogo.tsx (98%) create mode 100644 src/components/Navbar/components/NavbarSections.tsx create mode 100644 src/components/Navbar/components/NavbarSeparator.tsx rename src/{primitives => components}/Navbar/components/NavbarTitle.tsx (95%) create mode 100644 src/components/Navbar/index.ts create mode 100644 src/components/Navbar/utils/filterAndSortChildren.tsx delete mode 100644 src/primitives/Navbar/Navbar.tsx delete mode 100644 src/primitives/Navbar/components/index.ts delete mode 100644 src/primitives/Navbar/index.ts diff --git a/package.json b/package.json index 3d9e536..16428a0 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 93ce389..e3d5aee 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 ef684d6..6444281 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 0000000..ce03b0b --- /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 0000000..cbb6554 --- /dev/null +++ b/src/components/Navbar/components/NavbarGeneric.mdx @@ -0,0 +1,67 @@ +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) + +## Usage + +```tsx +import { + NavbarGeneric, + NavbarStartSection, + NavbarCenterSection, + NavbarEndSection, +} from "@/components/Navbar"; + +export const MyNavbar = () => ( + + + + + + + + + + + +); +``` + +```tsx +import { + NavbarGeneric, + NavbarStartSection, + NavbarCenterSection, + NavbarEndSection, +} from "@/components/Navbar"; + +export const MyNavbar = () => ( + + {/* Order doesn't matter */} + End + Start + + {/* Center section can be omitted */} + +); +``` + +## Props + + + +### Default + + diff --git a/src/components/Navbar/components/NavbarGeneric.stories.tsx b/src/components/Navbar/components/NavbarGeneric.stories.tsx new file mode 100644 index 0000000..c463c48 --- /dev/null +++ b/src/components/Navbar/components/NavbarGeneric.stories.tsx @@ -0,0 +1,134 @@ +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" }, + }, + }, + variant: { + control: "radio", + options: ["default"], + description: "Visual variant of the navbar", + table: { + defaultValue: { summary: "default" }, + }, + }, + }, +} 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 0000000..a3c1a7e --- /dev/null +++ b/src/components/Navbar/components/NavbarGeneric.tsx @@ -0,0 +1,51 @@ +"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: "flex h-16 w-full items-center justify-between px-20 py-2.5", + variants: { + behavior: { + static: "", + sticky: "sticky top-0 z-50", + fixed: "fixed left-0 right-0 top-0 z-50", + }, + variant: { + default: "bg-navbar shadow-navbar backdrop-blur-navbar", + }, + }, + defaultVariants: { + behavior: "sticky", + variant: "default", + }, +}); + +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 */ + behavior?: "static" | "sticky" | "fixed"; + /** Visual variant of the navbar */ + variant?: "default"; +} + +export const NavbarGeneric = React.forwardRef( + ({ className, behavior, variant, children, ...props }, ref) => { + const navbarStyle = navbarVariants({ behavior, variant }); + const filteredChildren = filterAndSortChildren(children); + return ( + + ); + }, +); +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 521a284..84874e1 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 0000000..892af76 --- /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 0000000..9e78179 --- /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 c6de7e0..e889175 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 0000000..3a6b81d --- /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 0000000..5c82dcc --- /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 6356bc2..b6c395a 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 4f93e2a..0000000 --- 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 f5c377a..0000000 --- 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 2134800..0000000 --- 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 04f29e7..82baa6f 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"; From b916f78dd5d9011b7ab99427e6c13d60c3ca416c Mon Sep 17 00:00:00 2001 From: Huss Martinez Date: Thu, 23 Jan 2025 14:04:41 +0100 Subject: [PATCH 4/5] chore: removed variant from navbargeneric --- .../Navbar/components/NavbarGeneric.stories.tsx | 8 -------- src/components/Navbar/components/NavbarGeneric.tsx | 12 +++--------- tailwind.config.ts | 6 ------ 3 files changed, 3 insertions(+), 23 deletions(-) diff --git a/src/components/Navbar/components/NavbarGeneric.stories.tsx b/src/components/Navbar/components/NavbarGeneric.stories.tsx index c463c48..f40efb8 100644 --- a/src/components/Navbar/components/NavbarGeneric.stories.tsx +++ b/src/components/Navbar/components/NavbarGeneric.stories.tsx @@ -17,14 +17,6 @@ const meta = { defaultValue: { summary: "sticky" }, }, }, - variant: { - control: "radio", - options: ["default"], - description: "Visual variant of the navbar", - table: { - defaultValue: { summary: "default" }, - }, - }, }, } satisfies Meta; diff --git a/src/components/Navbar/components/NavbarGeneric.tsx b/src/components/Navbar/components/NavbarGeneric.tsx index a3c1a7e..4edf2f8 100644 --- a/src/components/Navbar/components/NavbarGeneric.tsx +++ b/src/components/Navbar/components/NavbarGeneric.tsx @@ -9,20 +9,16 @@ import { cn } from "@/lib"; import { filterAndSortChildren } from "../utils/filterAndSortChildren"; const navbarVariants = tv({ - base: "flex h-16 w-full items-center justify-between px-20 py-2.5", + base: "flex h-16 w-full items-center justify-between bg-white/[0.03] px-20 py-2.5 backdrop-blur-[22px]", variants: { behavior: { static: "", sticky: "sticky top-0 z-50", fixed: "fixed left-0 right-0 top-0 z-50", }, - variant: { - default: "bg-navbar shadow-navbar backdrop-blur-navbar", - }, }, defaultVariants: { behavior: "sticky", - variant: "default", }, }); @@ -33,13 +29,11 @@ export interface NavbarGenericProps children?: React.ReactNode; /** Position of the navbar: static, sticky, or fixed */ behavior?: "static" | "sticky" | "fixed"; - /** Visual variant of the navbar */ - variant?: "default"; } export const NavbarGeneric = React.forwardRef( - ({ className, behavior, variant, children, ...props }, ref) => { - const navbarStyle = navbarVariants({ behavior, variant }); + ({ className, behavior, children, ...props }, ref) => { + const navbarStyle = navbarVariants({ behavior }); const filteredChildren = filterAndSortChildren(children); return (
); }, );