diff --git a/apps/docs/app/guides/layout.tsx b/apps/docs/app/guides/layout.tsx index 81d62a5a99b..1c479c6a6d7 100644 --- a/apps/docs/app/guides/layout.tsx +++ b/apps/docs/app/guides/layout.tsx @@ -12,7 +12,7 @@ const GuidesLayout = ({ children }: PropsWithChildren) => { return {children} } -const getMenuId = (pathname: string | null) => { +export const getMenuId = (pathname: string | null) => { pathname = (pathname ??= '').replace(/^\/guides\//, '') switch (true) { diff --git a/apps/docs/components/Breadcrumbs.tsx b/apps/docs/components/Breadcrumbs.tsx new file mode 100644 index 00000000000..f596cac7e49 --- /dev/null +++ b/apps/docs/components/Breadcrumbs.tsx @@ -0,0 +1,159 @@ +'use client' + +import React, { Fragment } from 'react' +import Link from 'next/link' +import { usePathname } from 'next/navigation' +import { + Breadcrumb_Shadcn_ as Breadcrumb, + BreadcrumbList_Shadcn_ as BreadcrumbList, + BreadcrumbItem_Shadcn_ as BreadcrumbItem, + BreadcrumbLink_Shadcn_ as BreadcrumbLink, + BreadcrumbSeparator_Shadcn_ as BreadcrumbSeparator, + BreadcrumbPage_Shadcn_ as BreadcrumbPage, + BreadcrumbEllipsis_Shadcn_ as BreadcrumbEllipsis, + Button, + cn, + Drawer, + DrawerClose, + DrawerContent, + DrawerFooter, + DrawerTrigger, + DropdownMenu, + DropdownMenuContent, + DropdownMenuItem, + DropdownMenuTrigger, +} from 'ui' +import { getMenuId } from '../app/guides/layout' +import { useBreakpoint } from 'common' +import * as NavItems from './Navigation/NavigationMenu/NavigationMenu.constants' + +const Breadcrumbs = ({ className }: { className?: string }) => { + const pathname = usePathname() + const menuId = getMenuId(pathname) + const menu = NavItems[menuId] + const breadcrumbs = findMenuItemByUrl(menu, pathname, []) + const [open, setOpen] = React.useState(false) + const isMobile = useBreakpoint('md') + + const ITEMS_TO_DISPLAY = isMobile ? 4 : 3 + + if (!breadcrumbs?.length || breadcrumbs?.length === 1) return null + + const appendedBreadcrumbs = breadcrumbs?.slice(-ITEMS_TO_DISPLAY + 1, isMobile ? -1 : undefined) + + return ( + + + {breadcrumbs.length >= ITEMS_TO_DISPLAY && ( + <> + + {breadcrumbs[0].url ? ( + + {breadcrumbs[0].title || breadcrumbs[0].name} + + ) : ( + {breadcrumbs[0].title || breadcrumbs[0].name} + )} + + + > + )} + {breadcrumbs.length > ITEMS_TO_DISPLAY && ( + <> + + {!isMobile ? ( + + + + + + {breadcrumbs.slice(1, -2).map((crumb, index) => ( + + {crumb.url ? ( + {crumb.title || crumb.name} + ) : ( + crumb.title || crumb.name + )} + + ))} + + + ) : ( + + + + + + + {breadcrumbs + .slice(1, -2) + .map((crumb) => + crumb.url ? ( + {crumb.title || crumb.name} + ) : ( + crumb.title || crumb.name + ) + )} + + + + Close + + + + + )} + + + > + )} + {appendedBreadcrumbs?.map((crumb, i) => ( + + + {crumb.url ? ( + + {crumb.title || crumb.name} + + ) : ( + {crumb.title || crumb.name} + )} + + + + ))} + + + ) +} + +function findMenuItemByUrl(menu, targetUrl, parents = []) { + // If the menu has items, recursively search through them + if (menu.items) { + for (let item of menu.items) { + const result = findMenuItemByUrl(item, targetUrl, [...parents, menu]) + if (result) { + return result + } + } + } + + // Check if the current menu object itself has the target URL + if (menu.url === targetUrl) { + return [...parents, menu] + } + + // If the URL is not found, return null + return null +} + +export default Breadcrumbs diff --git a/apps/docs/components/Navigation/NavigationMenu/NavigationMenu.constants.ts b/apps/docs/components/Navigation/NavigationMenu/NavigationMenu.constants.ts index e8af9a15f17..82e4e7c9904 100644 --- a/apps/docs/components/Navigation/NavigationMenu/NavigationMenu.constants.ts +++ b/apps/docs/components/Navigation/NavigationMenu/NavigationMenu.constants.ts @@ -245,6 +245,7 @@ export const REFERENCES: References = { export const gettingstarted: NavMenuConstant = { icon: 'getting-started', title: 'Start with Supabase', + url: '/guides/getting-started', items: [ { name: 'Features', url: '/guides/getting-started/features' }, { name: 'Architecture', url: '/guides/getting-started/architecture' }, @@ -1202,7 +1203,7 @@ export const functions: NavMenuConstant = { }, { name: 'Examples', - url: '/guides/functions/examples', + url: undefined, items: [ { name: 'CORS support for invoking from the browser', diff --git a/apps/docs/components/Navigation/NavigationMenu/NavigationMenu.tsx b/apps/docs/components/Navigation/NavigationMenu/NavigationMenu.tsx index cdb7407a6d8..95dc17e710f 100644 --- a/apps/docs/components/Navigation/NavigationMenu/NavigationMenu.tsx +++ b/apps/docs/components/Navigation/NavigationMenu/NavigationMenu.tsx @@ -234,7 +234,7 @@ const menus: Menu[] = [ }, ] -function getMenuById(id: MenuId) { +export function getMenuById(id: MenuId) { return menus.find((menu) => menu.id === id) } diff --git a/apps/docs/components/Navigation/NavigationMenu/NavigationMenuGuideList.tsx b/apps/docs/components/Navigation/NavigationMenu/NavigationMenuGuideList.tsx index 3d9b5d6872d..dc1d852bb3d 100644 --- a/apps/docs/components/Navigation/NavigationMenu/NavigationMenuGuideList.tsx +++ b/apps/docs/components/Navigation/NavigationMenu/NavigationMenuGuideList.tsx @@ -3,7 +3,6 @@ import * as Accordion from '@radix-ui/react-accordion' import * as NavItems from './NavigationMenu.constants' -import { getPathWithoutHash } from './NavigationMenu.utils' import NavigationMenuGuideListItems from './NavigationMenuGuideListItems' import { usePathname } from 'next/navigation' diff --git a/apps/docs/features/docs/GuidesMdx.template.tsx b/apps/docs/features/docs/GuidesMdx.template.tsx index d22b2c3c1a4..46a63b23424 100644 --- a/apps/docs/features/docs/GuidesMdx.template.tsx +++ b/apps/docs/features/docs/GuidesMdx.template.tsx @@ -13,6 +13,7 @@ import { components } from '~/features/docs/mdx.shared' import type { WithRequired } from '~/features/helpers.types' import { type GuideFrontmatter } from '~/lib/docs' import { MDXProviderGuides } from './GuidesMdx.client' +import Breadcrumbs from '~/components/Breadcrumbs' const codeHikeOptions: CodeHikeConfig = { theme: codeHikeTheme, @@ -120,6 +121,7 @@ const GuideTemplate = ({ meta, content, children, editLink, mdxOptions }: GuideT hideToc ? 'col-span-12' : 'col-span-12 md:col-span-9' )} > + )} diff --git a/apps/docs/layouts/DefaultLayout.tsx b/apps/docs/layouts/DefaultLayout.tsx index f6b3f8bf230..39224810673 100644 --- a/apps/docs/layouts/DefaultLayout.tsx +++ b/apps/docs/layouts/DefaultLayout.tsx @@ -3,4 +3,4 @@ import { type FC, type PropsWithChildren } from 'react' export const LayoutMainContent: FC> = ({ className, children, -}) => {children} +}) => {children} diff --git a/apps/docs/layouts/ref/RefSubLayout.tsx b/apps/docs/layouts/ref/RefSubLayout.tsx index 80eecf7a158..1e1ef0e3f20 100644 --- a/apps/docs/layouts/ref/RefSubLayout.tsx +++ b/apps/docs/layouts/ref/RefSubLayout.tsx @@ -122,7 +122,7 @@ const StickyHeader: FC = ({ icon, ...props }) => { id={props.slug} data-ref-id={props.id} className={cn( - 'text-2xl font-medium text-foreground scroll-mt-[calc(33px+2rem)] lg:scroll-mt-[calc(var(--header-height)+1px+4rem)]', + 'text-2xl font-medium text-foreground scroll-mt-[calc(32px+2rem)] lg:scroll-mt-[calc(var(--header-height)+1px+4rem)]', !icon && 'mb-8', props.monoFont && 'font-mono' )} diff --git a/packages/ui/index.tsx b/packages/ui/index.tsx index 8135a4f5f27..9d61eeba8ee 100644 --- a/packages/ui/index.tsx +++ b/packages/ui/index.tsx @@ -191,6 +191,16 @@ export { Input as Input_Shadcn_ } from './src/components/shadcn/ui/input' export { Button as Button_Shadcn_ } from './src/components/shadcn/ui/button' +export { + Breadcrumb as Breadcrumb_Shadcn_, + BreadcrumbItem as BreadcrumbItem_Shadcn_, + BreadcrumbLink as BreadcrumbLink_Shadcn_, + BreadcrumbList as BreadcrumbList_Shadcn_, + BreadcrumbEllipsis as BreadcrumbEllipsis_Shadcn_, + BreadcrumbPage as BreadcrumbPage_Shadcn_, + BreadcrumbSeparator as BreadcrumbSeparator_Shadcn_, +} from './src/components/shadcn/ui/breadcrumb' + export { TextArea as TextArea_Shadcn_ } from './src/components/shadcn/ui/text-area' export { Label as Label_Shadcn_ } from './src/components/shadcn/ui/label' diff --git a/packages/ui/src/components/shadcn/ui/breadcrumb.tsx b/packages/ui/src/components/shadcn/ui/breadcrumb.tsx new file mode 100644 index 00000000000..7bddf8fa745 --- /dev/null +++ b/packages/ui/src/components/shadcn/ui/breadcrumb.tsx @@ -0,0 +1,117 @@ +import * as React from 'react' +import { ChevronRightIcon } from 'lucide-react' +import { Slot } from '@radix-ui/react-slot' +import { cn } from '../../../lib/utils' + +const Breadcrumb = React.forwardRef< + HTMLElement, + React.ComponentPropsWithoutRef<'nav'> & { + separator?: React.ReactNode + } +>(({ ...props }, ref) => ) +Breadcrumb.displayName = 'Breadcrumb' + +const BreadcrumbList = React.forwardRef>( + ({ className, ...props }, ref) => ( + + ) +) +BreadcrumbList.displayName = 'BreadcrumbList' + +const BreadcrumbItem = React.forwardRef>( + ({ className, ...props }, ref) => ( + + ) +) +BreadcrumbItem.displayName = 'BreadcrumbItem' + +const BreadcrumbLink = React.forwardRef< + HTMLAnchorElement, + React.ComponentPropsWithoutRef<'a'> & { + asChild?: boolean + } +>(({ asChild, className, ...props }, ref) => { + const Comp = asChild ? Slot : 'a' + + return ( + + ) +}) +BreadcrumbLink.displayName = 'BreadcrumbLink' + +const BreadcrumbPage = React.forwardRef>( + ({ className, ...props }, ref) => ( + + ) +) +BreadcrumbPage.displayName = 'BreadcrumbPage' + +const BreadcrumbSeparator = ({ children, className, ...props }: React.ComponentProps<'li'>) => ( + svg]:size-3.5', className)} + {...props} + > + {children ?? } + +) +BreadcrumbSeparator.displayName = 'BreadcrumbSeparator' + +const BreadcrumbEllipsis = ({ className, ...props }: React.ComponentProps<'span'>) => ( + + + + + More + +) +BreadcrumbEllipsis.displayName = 'BreadcrumbEllipsis' + +export { + Breadcrumb, + BreadcrumbList, + BreadcrumbItem, + BreadcrumbLink, + BreadcrumbPage, + BreadcrumbSeparator, + BreadcrumbEllipsis, +} diff --git a/packages/ui/src/components/shadcn/ui/drawer.tsx b/packages/ui/src/components/shadcn/ui/drawer.tsx index b37bb45a9ac..09626cdd2e4 100644 --- a/packages/ui/src/components/shadcn/ui/drawer.tsx +++ b/packages/ui/src/components/shadcn/ui/drawer.tsx @@ -31,19 +31,20 @@ DrawerOverlay.displayName = DrawerPrimitive.Overlay.displayName const DrawerContent = React.forwardRef< React.ElementRef, - React.ComponentPropsWithoutRef ->(({ className, children, ...props }, ref) => ( + React.ComponentPropsWithoutRef & { showHandle?: boolean } +>(({ className, children, showHandle = true, ...props }, ref) => ( - + {showHandle && } {children}