From 3bc453601da7c0cfc352c403d438bd1bc9bb4c47 Mon Sep 17 00:00:00 2001 From: Damien Schneider <74979845+damien-schneider@users.noreply.github.com> Date: Thu, 31 Oct 2024 02:00:01 +0100 Subject: [PATCH] Add-new-bottom-navigation-menu-&-home-page (#50) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * :sparkles: add new floating dock menu * :art: improve scrollarea to accept viewport * :sparkles: display sidemenu corresponding to category selected * ✅ remove outdated HomePage tests and update Sidemenu tests to include InfoMenuList * :art: add components to preview only variants * :sparkles: update dev script to use turbo mode for improved performance --- apps/website/__tests__/home-h1.test.tsx | 35 --- apps/website/__tests__/sidemenu.test.tsx | 13 +- apps/website/package.json | 2 +- apps/website/src/app/(components)/layout.tsx | 26 +- .../src/app/floating-docks-component.tsx | 261 ++++++++++++++++++ apps/website/src/app/layout-floating-dock.tsx | 92 ++++++ apps/website/src/app/layout.tsx | 33 +-- apps/website/src/app/page.tsx | 132 +++++---- .../component-tab-renderer.tsx | 25 +- .../component-wrapper/github-edit-button.tsx | 2 +- .../main-menus/menu-category-wrapper.tsx | 18 +- .../components/navigation/desktop-menu.tsx | 12 +- .../components/navigation/info-menu-list.tsx | 31 +++ .../src/components/navigation/mobile-menu.tsx | 79 ------ .../components/navigation/navigation-item.tsx | 60 +--- .../components/navigation/navigation-menu.tsx | 23 +- ...opy-to-clipboard-code-snippet-dropdown.tsx | 146 +++++----- apps/website/src/ui/shadcn/scrollarea.tsx | 110 ++++---- .../src/ui/star-github-project-button.tsx | 6 +- apps/website/src/ui/theme-switcher.tsx | 46 --- .../battery-indicator/battery-indicator.tsx | 2 +- .../common-ui/buttons/buttons.category.tsx | 10 +- ...component.shiny-rotating-border-button.tsx | 73 +++++ .../preview.shiny-rotating-border-button.tsx | 5 + .../shiny-rotating-border-button/variant1.tsx | 62 ----- .../hooks/use-step/preview-use-step.tsx | 4 +- .../mock-ups/laptops/component.mackbook.tsx | 39 +++ .../mock-ups/laptops/preview.mackbook.tsx | 5 + .../other/mock-ups/laptops/variant1.tsx | 22 -- .../other/mock-ups/mock-ups.category.tsx | 7 +- 30 files changed, 820 insertions(+), 561 deletions(-) delete mode 100644 apps/website/__tests__/home-h1.test.tsx create mode 100644 apps/website/src/app/floating-docks-component.tsx create mode 100644 apps/website/src/app/layout-floating-dock.tsx create mode 100644 apps/website/src/components/navigation/info-menu-list.tsx delete mode 100644 apps/website/src/components/navigation/mobile-menu.tsx delete mode 100644 apps/website/src/ui/theme-switcher.tsx create mode 100644 packages/ui/cuicui/common-ui/buttons/shiny-rotating-border-button/component.shiny-rotating-border-button.tsx create mode 100644 packages/ui/cuicui/common-ui/buttons/shiny-rotating-border-button/preview.shiny-rotating-border-button.tsx delete mode 100644 packages/ui/cuicui/common-ui/buttons/shiny-rotating-border-button/variant1.tsx create mode 100644 packages/ui/cuicui/other/mock-ups/laptops/component.mackbook.tsx create mode 100644 packages/ui/cuicui/other/mock-ups/laptops/preview.mackbook.tsx delete mode 100644 packages/ui/cuicui/other/mock-ups/laptops/variant1.tsx diff --git a/apps/website/__tests__/home-h1.test.tsx b/apps/website/__tests__/home-h1.test.tsx deleted file mode 100644 index 6312b9e..0000000 --- a/apps/website/__tests__/home-h1.test.tsx +++ /dev/null @@ -1,35 +0,0 @@ -import { render, screen } from "@testing-library/react"; -import { describe, expect, it } from "vitest"; -import HomePage from "#/src/app/page"; - -const gettingStartedRegex = /getting-started$/; - -describe("Page", () => { - render(); - it("should have a heading", () => { - const heading = screen.getByRole("heading", { - level: 1, - name: "CuiCui - Copy Paste quality React component", - }); - expect(heading).toBeDefined(); - }); - it("should have href attribute of the 'Contribute' element set to https://cuicui.featurebase.app/", () => { - const contributeElement = screen.getByTestId( - "home-navigation-link-Contribute", - ); - - // Check if the href attribute of the 'Contribute' element is https://cuicui.featurebase.app/ - const hrefValue = contributeElement.getAttribute("href"); - expect(hrefValue).toBe("https://cuicui.featurebase.app/"); - }); - - it("should have href attribute of the 'Getting started' element to finish by getting-started", () => { - const contributeElement = screen.getByTestId( - "home-navigation-link-Getting Started", - ); - - // Check if the href attribute of the 'Getting started' element finishes by getting-started - const hrefValue = contributeElement.getAttribute("href"); - expect(hrefValue).toMatch(gettingStartedRegex); - }); -}); diff --git a/apps/website/__tests__/sidemenu.test.tsx b/apps/website/__tests__/sidemenu.test.tsx index fef637c..8590a6d 100644 --- a/apps/website/__tests__/sidemenu.test.tsx +++ b/apps/website/__tests__/sidemenu.test.tsx @@ -1,16 +1,25 @@ import { render, screen } from "@testing-library/react"; import { describe, expect, it, vi } from "vitest"; import NavigationMenu from "#/src/components/navigation/navigation-menu"; +import InfoMenuList from "#/src/components/navigation/info-menu-list"; const gettingStartedRegex = /getting-started$/; vi.mock("next/navigation", () => ({ - usePathname: () => [], + usePathname: () => { + return "/common-ui"; + }, useSelectedLayoutSegments: () => [], })); describe("Sidemenu component", () => { - render(); + render( +
+ + +
, + ); + it("should have href attribute of the 'Contribute' element set to https://cuicui.featurebase.app/", () => { const contributeElement = screen.getByTestId("navigation-link-Contribute"); diff --git a/apps/website/package.json b/apps/website/package.json index fd00cfb..ebcf925 100644 --- a/apps/website/package.json +++ b/apps/website/package.json @@ -89,7 +89,7 @@ "publisher": "Damien Schneider", "scripts": { "build": "next build", - "dev": "next dev", + "dev": "next dev --turbo", "start": "next start", "format:check": "biome format --check ./src", "format:write": "biome format --write ./src", diff --git a/apps/website/src/app/(components)/layout.tsx b/apps/website/src/app/(components)/layout.tsx index 8cf35fe..17d9353 100644 --- a/apps/website/src/app/(components)/layout.tsx +++ b/apps/website/src/app/(components)/layout.tsx @@ -1,7 +1,29 @@ +import { DesktopSideMenu } from "#/src/components/navigation/desktop-menu"; + +import { AddressBar } from "#/src/ui/address-bar"; +import StarGithubProjectButton from "#/src/ui/star-github-project-button"; import type React from "react"; export default function ComponentsLayout({ - children, + children, }: Readonly<{ children: React.ReactNode }>) { - return
{children}
; + return ( +
+ + +
+ {/* Add overflow-auto if layout width problems */} + + + +
+
{children}
+
+ +
+ +
+
+
+ ); } diff --git a/apps/website/src/app/floating-docks-component.tsx b/apps/website/src/app/floating-docks-component.tsx new file mode 100644 index 0000000..2530cd0 --- /dev/null +++ b/apps/website/src/app/floating-docks-component.tsx @@ -0,0 +1,261 @@ +"use client"; +import { useOnClickOutside } from "@/cuicui/hooks/use-click-outside/use-click-outside"; +/** + * Note: Use position fixed according to your needs + * Desktop navbar is better positioned at the bottom + * Mobile navbar is better positioned at bottom right. + **/ + +import { cn } from "@/cuicui/utils/cn/cn"; +import { + AnimatePresence, + type MotionValue, + motion, + useMotionValue, + useSpring, + useTransform, +} from "framer-motion"; +import { ListCollapseIcon, type LucideIcon } from "lucide-react"; +import Link from "next/link"; +import { usePathname } from "next/navigation"; +import { useEffect, useRef, useState } from "react"; + +export const FloatingDock = ({ + items, + desktopClassName, + mobileClassName, +}: { + items: { + title: string; + Icon: LucideIcon; + href: string; + }[]; + desktopClassName?: string; + mobileClassName?: string; +}) => { + return ( + <> + + + + ); +}; + +const FloatingDockMobile = ({ + items, + className, +}: { + items: { + title: string; + Icon: LucideIcon; + href: string; + }[]; + className?: string; +}) => { + const ref = useRef(null); + const handleClickOutside = (event: MouseEvent | TouchEvent | FocusEvent) => { + setOpen(false); + }; + + const pathname = usePathname(); + + useEffect(() => { + if (!pathname) { + return; + } + setOpen(false); + }, [pathname]); + + useOnClickOutside(ref, handleClickOutside); + const [open, setOpen] = useState(false); + return ( +
+ + {open && ( + + {items.map((item, idx) => ( + + setOpen(false)} + > + +

+ {item.title} +

+ +
+ ))} +
+ )} +
+ +
+ ); +}; + +const FloatingDockDesktop = ({ + items, + className, +}: { + items: { title: string; Icon: LucideIcon; href: string }[]; + className?: string; +}) => { + const mouseX = useMotionValue(Number.POSITIVE_INFINITY); + return ( + mouseX.set(e.pageX)} + onMouseLeave={() => mouseX.set(Number.POSITIVE_INFINITY)} + className={cn( + "mx-auto hidden md:flex h-14 gap-2 items-end rounded-full bg-neutral-50 dark:bg-neutral-900 px-2 pb-2 border border-neutral-500/20", + className, + )} + > + {items.map((item) => ( + + ))} + + ); +}; + +function IconContainer({ + mouseX, + title, + Icon, + href, +}: Readonly<{ + mouseX: MotionValue; + title: string; + Icon: LucideIcon; + href: string; +}>) { + const pathname = usePathname(); + const [isActive, setIsActive] = useState(false); + + useEffect(() => { + if (href === "/") { + setIsActive(href === pathname); + } else { + setIsActive(pathname.includes(href)); + } + }, [pathname, href]); + + const ref = useRef(null); + + const distance = useTransform(mouseX, (val) => { + const bounds = ref.current?.getBoundingClientRect() ?? { x: 0, width: 0 }; + + return val - bounds.x - bounds.width / 2; + }); + + const widthTransform = useTransform(distance, [-150, 0, 150], [60, 80, 60]); + const heightTransform = useTransform(distance, [-150, 0, 150], [40, 60, 40]); + + const widthTransformIcon = useTransform( + distance, + [-150, 0, 150], + [20, 40, 20], + ); + const heightTransformIcon = useTransform( + distance, + [-150, 0, 150], + [20, 40, 20], + ); + + const width = useSpring(widthTransform, { + mass: 0.1, + stiffness: 300, + damping: 12, + }); + const height = useSpring(heightTransform, { + mass: 0.1, + stiffness: 300, + damping: 12, + }); + + const widthIcon = useSpring(widthTransformIcon, { + mass: 0.1, + stiffness: 300, + damping: 12, + }); + const heightIcon = useSpring(heightTransformIcon, { + mass: 0.1, + stiffness: 300, + damping: 12, + }); + + const [hovered, setHovered] = useState(false); + + return ( + + setHovered(true)} + onMouseLeave={() => setHovered(false)} + className={cn( + "aspect-square rounded-full border border-neutral-400/20 backdrop-blur-2xl flex items-center justify-center relative", + isActive + ? "bg-neutral-800 dark:bg-neutral-100" + : "bg-neutral-100 dark:bg-neutral-800", + )} + > + + {hovered && ( + // ------ Tooltip ------ // + + {title} + + )} + + + + + + + ); +} diff --git a/apps/website/src/app/layout-floating-dock.tsx b/apps/website/src/app/layout-floating-dock.tsx new file mode 100644 index 0000000..a2ec42a --- /dev/null +++ b/apps/website/src/app/layout-floating-dock.tsx @@ -0,0 +1,92 @@ +"use client"; + +import { SectionsList } from "@cuicui/ui"; + +import { FloatingDock } from "#/src/app/floating-docks-component"; +import { HomeIcon, MoonIcon, SunIcon } from "lucide-react"; +import { usePathname } from "next/navigation"; +import { useTheme } from "next-themes"; +import { cn } from "#/src/utils/cn"; +import Link from "next/link"; + +export function CuicuiFloatingDock() { + const pathName = usePathname(); + const { setTheme, theme, resolvedTheme } = useTheme(); + + const handleSwitchTheme = () => { + if (resolvedTheme === "dark") { + setTheme("light"); + } + if (resolvedTheme === "light") { + setTheme("dark"); + } + }; + return ( +
+
+ + + +
+ +
+ +
+
+ ); +} + +const sectionLinks = SectionsList.map((section) => { + return { + title: section.name, + Icon: section.icon, + href: `/${section.slug}`, + }; +}); + +const homeLink = { + title: "Home", + icon: ( + + ), + href: "/", +}; +const links = [homeLink, ...sectionLinks]; diff --git a/apps/website/src/app/layout.tsx b/apps/website/src/app/layout.tsx index cb25c45..6481f0d 100644 --- a/apps/website/src/app/layout.tsx +++ b/apps/website/src/app/layout.tsx @@ -3,13 +3,13 @@ import { Toaster } from "sonner"; import "#/src/styles/globals.css"; import { DesktopSideMenu } from "#/src/components/navigation/desktop-menu"; import { AddressBar } from "#/src/ui/address-bar"; -import { MobileMenu } from "../components/navigation/mobile-menu"; import { DM_Sans } from "next/font/google"; import type { ReactNode } from "react"; import Providers from "#/src/app/providers"; import StarGithubProjectButton from "#/src/ui/star-github-project-button"; import PlausibleScripts from "#/src/components/analytics/plausible-scripts"; +import { CuicuiFloatingDock } from "#/src/app/layout-floating-dock"; const font = DM_Sans({ subsets: ["latin"], display: "swap", @@ -79,35 +79,8 @@ export default function RootLayout({ -
- - {/*
-
-
-
-
*/} -
- {/* Add overflow-auto if layout width problems */} - - - {/* */} -
- {/* Move overflow-auto to the previous comment if problems occurs */} - {children} -
- {/*
*/} -
- -
- - -
-
+ {children} + diff --git a/apps/website/src/app/page.tsx b/apps/website/src/app/page.tsx index 01cac1c..2e10106 100644 --- a/apps/website/src/app/page.tsx +++ b/apps/website/src/app/page.tsx @@ -6,59 +6,93 @@ import { SectionsList } from "@cuicui/ui"; import { firstMenuSection } from "#/src/lib/first-menu-section"; import { MainMenusGradientCard } from "@cuicui/ui/cuicui/other/cursors/dynamic-cards/gradient-card"; import { MainMenuCardContent } from "./card"; +import { MacbookMockUp } from "@/cuicui/other/mock-ups/laptops/component.mackbook"; +import { PreviewRecursiveTree } from "@/cuicui/application-ui/tree/recursive-tree/preview-recursive-tree"; +import { PreviewBatteryIndicator } from "@/cuicui/application-ui/battery/battery-indicator/preview-battery-indicator"; +import { ShinyRotatingBorderButton } from "@/cuicui/common-ui/buttons/shiny-rotating-border-button/component.shiny-rotating-border-button"; +import { TextEffectWrapper } from "@/cuicui/other/transition-wrappers/text-effect-wrapper/text-effect-wrapper"; +import { DotsPattern } from "@/cuicui/other/patterns/dots-pattern/dots-pattern"; +import { PreviewUseMeasure } from "@/cuicui/hooks/use-measure/preview.use-measure"; export default function HomePage() { return ( -
-

CuiCui - Copy Paste quality React component

-

- CuiCui is a modern UI copy paste library that helps you build beautiful - websites and applications. Its name "CuiCui" is for Quick UI. -

-
-

Explore the component categories

- - {firstMenuSection.categoryList.map((category) => { - return ( - - - - ); - })} - +
+ +
+ +
+
+ +
+ +
+
+ + Cuicui + +
+

+ + Every UI + + + components, hooks, tools, ... + + + you need + +

+ + More than 100 variants of Open-Source components, hooks and tools made + for React. Just Copy Paste. + - {SectionsList.map((section) => ( - - {section.type !== "page" && - section.categoriesList.map((category) => { - return ( - - - - - - ); - })} - - ))} + + Start Copy Pasting + +
+
+ +
+
+
-
+ ); } diff --git a/apps/website/src/components/component-wrapper/component-tab-renderer.tsx b/apps/website/src/components/component-wrapper/component-tab-renderer.tsx index 53255e3..3ae1bc6 100644 --- a/apps/website/src/components/component-wrapper/component-tab-renderer.tsx +++ b/apps/website/src/components/component-wrapper/component-tab-renderer.tsx @@ -15,7 +15,11 @@ import { ResizablePanel, ResizablePanelGroup, } from "#/src/ui/shadcn/resizable"; -import { ScrollArea, ScrollBar } from "#/src/ui/shadcn/scrollarea"; +import { + ScrollArea, + ScrollAreaViewport, + ScrollBar, +} from "#/src/ui/shadcn/scrollarea"; import { cn } from "#/src/utils/cn"; import { getContainerHeightClass } from "#/src/components/component-wrapper/get-container-height-class"; const tabs = [ @@ -143,16 +147,17 @@ export default function ComponentTabRenderer({ getContainerHeightClass({ size }), )} - classNameViewport="p-3" > - - - {tab === "code-preview" && ( - - )} - {tab === "code-component" && componentCode && ( - - )} + + + + {tab === "code-preview" && ( + + )} + {tab === "code-component" && componentCode && ( + + )} + {tab === "code-preview" && ( diff --git a/apps/website/src/components/component-wrapper/github-edit-button.tsx b/apps/website/src/components/component-wrapper/github-edit-button.tsx index 7791862..0a0958c 100644 --- a/apps/website/src/components/component-wrapper/github-edit-button.tsx +++ b/apps/website/src/components/component-wrapper/github-edit-button.tsx @@ -17,7 +17,7 @@ export default function GithubEditButton({ return ( Edit on Github diff --git a/apps/website/src/components/main-menus/menu-category-wrapper.tsx b/apps/website/src/components/main-menus/menu-category-wrapper.tsx index 2c6c327..823f700 100644 --- a/apps/website/src/components/main-menus/menu-category-wrapper.tsx +++ b/apps/website/src/components/main-menus/menu-category-wrapper.tsx @@ -1,14 +1,16 @@ import type { ReactNode } from "react"; export default function MenuSectionWrapper({ - name, - children, + name, + children, }: Readonly<{ name: string; children: ReactNode }>) { - return ( -
-

{name}

+ return ( +
+

{name}

-
{children}
-
- ); +
+ {children} +
+
+ ); } diff --git a/apps/website/src/components/navigation/desktop-menu.tsx b/apps/website/src/components/navigation/desktop-menu.tsx index 0503fb9..47589bd 100644 --- a/apps/website/src/components/navigation/desktop-menu.tsx +++ b/apps/website/src/components/navigation/desktop-menu.tsx @@ -7,11 +7,11 @@ import { cn } from "../../utils/cn"; import Byline from "../../ui/byline"; -import { ScrollArea } from "../../ui/shadcn/scrollarea"; +import { ScrollArea, ScrollAreaViewport } from "../../ui/shadcn/scrollarea"; import StarGithubProjectButton from "../../ui/star-github-project-button"; -import ThemeSwitcher from "../../ui/theme-switcher"; import { SearchMenu } from "../search-menu/search-menu"; import NavigationMenu from "./navigation-menu"; +import InfoMenuList from "#/src/components/navigation/info-menu-list"; export function DesktopSideMenu({ className, @@ -35,13 +35,15 @@ export function DesktopSideMenu({ Cuicui - Best all in one React component library -
- - + + + + + diff --git a/apps/website/src/components/navigation/info-menu-list.tsx b/apps/website/src/components/navigation/info-menu-list.tsx new file mode 100644 index 0000000..ca77419 --- /dev/null +++ b/apps/website/src/components/navigation/info-menu-list.tsx @@ -0,0 +1,31 @@ +import { NavigationAnimatedBackground } from "#/src/components/navigation/navigation-animated-background"; +import { + GlobalNavItem, + SectionWrapper, +} from "#/src/components/navigation/navigation-item"; +import { firstMenuSection } from "#/src/lib/first-menu-section"; +import React from "react"; + +export default function InfoMenuList() { + return ( + + + {firstMenuSection.categoryList.map((category, _index) => ( +
  • + +
  • + ))} +
    +
    + ); +} diff --git a/apps/website/src/components/navigation/mobile-menu.tsx b/apps/website/src/components/navigation/mobile-menu.tsx deleted file mode 100644 index 8442b5e..0000000 --- a/apps/website/src/components/navigation/mobile-menu.tsx +++ /dev/null @@ -1,79 +0,0 @@ -"use client"; - -import { MenuIcon } from "lucide-react"; -import Byline from "../../ui/byline"; -import GradientContainer from "../../ui/gradient-container"; - -import Image from "next/image"; -import Link from "next/link"; -import { usePathname } from "next/navigation"; -import { useEffect, useState } from "react"; -import { Drawer } from "vaul"; -import LogoLarge from "#/src/assets/logo/logo-large.png"; -import { cn } from "#/src/utils/cn"; -import ThemeSwitcher from "../../ui/theme-switcher"; -import NavigationMenu from "./navigation-menu"; -export function MobileMenu({ className }: Readonly<{ className?: string }>) { - const [isDrawerOpen, setIsDrawerOpen] = useState(false); - const pathname = usePathname(); - - useEffect(() => { - if (!pathname) { - return; - } - setIsDrawerOpen(false); - }, [pathname]); - - return ( - setIsDrawerOpen(isOpen)} - open={isDrawerOpen} - > -
    - - - Cuicui logo - - - - - - - - -
    - - - - - -
    - -
    - -
    -
    - {/* */} -
    - ); -} diff --git a/apps/website/src/components/navigation/navigation-item.tsx b/apps/website/src/components/navigation/navigation-item.tsx index 8a64393..c36d22c 100644 --- a/apps/website/src/components/navigation/navigation-item.tsx +++ b/apps/website/src/components/navigation/navigation-item.tsx @@ -6,17 +6,7 @@ import { } from "lucide-react"; import Link from "next/link"; import { usePathname, useSelectedLayoutSegments } from "next/navigation"; -import { - useEffect, - useState, - type AnchorHTMLAttributes, - type ReactNode, -} from "react"; -import { - Disclosure, - DisclosureContent, - DisclosureTrigger, -} from "#/src/components/navigation/disclosure"; +import type { AnchorHTMLAttributes, ReactNode } from "react"; import GradientContainer from "#/src/ui/gradient-container"; import GradientText from "#/src/ui/gradient-text"; import { cn } from "#/src/utils/cn"; @@ -90,64 +80,22 @@ export function GlobalNavItem({ export const SectionWrapper = ({ children, name, - disclosure = true, Icon, className, sectionSlug, }: { children: ReactNode; name: string; - disclosure?: boolean; Icon?: LucideIcon; className?: string; sectionSlug: SectionType["slug"]; }) => { - const [isOpen, setOpen] = useState(false); const pathName = usePathname(); - useEffect(() => { - const doesPathNameIncludeSectionSlug = pathName.includes(sectionSlug); - if (doesPathNameIncludeSectionSlug) { - setOpen(true); - } - }, [pathName, sectionSlug]); - if (disclosure) { - return ( - - - - - - {children} - - - //
    - //
    - //

    {name}

    - //
    - // {children} - //
    - ); + if (!pathName.includes(sectionSlug)) { + return null; } + return (
    diff --git a/apps/website/src/components/navigation/navigation-menu.tsx b/apps/website/src/components/navigation/navigation-menu.tsx index 3ae6d6e..3f9e751 100644 --- a/apps/website/src/components/navigation/navigation-menu.tsx +++ b/apps/website/src/components/navigation/navigation-menu.tsx @@ -77,28 +77,7 @@ export default function NavigationMenu({ } return ( -