diff --git a/messages/en.json b/messages/en.json index dfbff26..9e5f377 100644 --- a/messages/en.json +++ b/messages/en.json @@ -65,7 +65,8 @@ "categories": "Categories", "tableOfContents": "Table of Contents", "all": "All", - "noPostsFound": "No posts found" + "noPostsFound": "No posts found", + "allPosts": "All Posts" }, "mail": { "common": { diff --git a/messages/zh.json b/messages/zh.json index 8fb61c1..6d81209 100644 --- a/messages/zh.json +++ b/messages/zh.json @@ -65,7 +65,8 @@ "categories": "分类", "tableOfContents": "目录", "all": "全部", - "noPostsFound": "没有找到文章" + "noPostsFound": "没有找到文章", + "allPosts": "全部文章" }, "mail": { "common": { diff --git a/public/images/avatars/haitang.png b/public/images/avatars/haitang.png index 3a0eafb..7fabd8c 100644 Binary files a/public/images/avatars/haitang.png and b/public/images/avatars/haitang.png differ diff --git a/src/app/[locale]/(marketing)/blog/[...slug]/page.tsx b/src/app/[locale]/(marketing)/blog/[...slug]/page.tsx index ebd5804..782558c 100644 --- a/src/app/[locale]/(marketing)/blog/[...slug]/page.tsx +++ b/src/app/[locale]/(marketing)/blog/[...slug]/page.tsx @@ -1,10 +1,10 @@ import AllPostsButton from '@/components/blog/all-posts-button'; import { BlogToc } from '@/components/blog/blog-toc'; -import { Mdx } from '@/components/marketing/blog/mdx-component'; +import { Mdx } from '@/components/shared/mdx-component'; import { LocaleLink } from '@/i18n/navigation'; import { getTableOfContents } from '@/lib/toc'; import { getBaseUrl } from '@/lib/urls/get-base-url'; -import { getLocaleDate } from '@/lib/utils'; +import { estimateReadingTime, getLocaleDate } from '@/lib/utils'; import type { NextPageProps } from '@/types/next-page-props'; import { allPosts } from 'content-collections'; import type { Metadata } from 'next'; @@ -113,6 +113,12 @@ export default async function BlogPostPage(props: NextPageProps) { )} + {/* blog post date */} +
+

{date}

+

{estimateReadingTime(post.body.raw)}

+
+ {/* blog post title */}

{post.title}

@@ -137,7 +143,7 @@ export default async function BlogPostPage(props: NextPageProps) {

{t("publisher")}

-
+
{post.author?.avatar && ( )}
-
- {post.author?.name} - -

{date}

-
+ {post.author?.name}
diff --git a/src/app/[locale]/(marketing)/layout.tsx b/src/app/[locale]/(marketing)/layout.tsx index 4e11e57..c5e7a87 100644 --- a/src/app/[locale]/(marketing)/layout.tsx +++ b/src/app/[locale]/(marketing)/layout.tsx @@ -1,6 +1,6 @@ import { marketingConfig } from "@/config/marketing"; import { Footer } from "@/components/layout/footer"; -import { Navbar } from "@/components/marketing/navbar"; +import { Navbar } from "@/components/layout/navbar"; export default function MarketingLayout({ children }: { children: React.ReactNode }) { return ( diff --git a/src/app/[locale]/layout.tsx b/src/app/[locale]/layout.tsx index 96eab56..a080bdc 100644 --- a/src/app/[locale]/layout.tsx +++ b/src/app/[locale]/layout.tsx @@ -1,8 +1,5 @@ import { fontSourceSans, fontSourceSerif4 } from "@/assets/fonts"; -import { Footer } from '@/components/layout/footer'; -import { Navbar } from '@/components/marketing/navbar'; import { TailwindIndicator } from '@/components/tailwind-indicator'; -import { marketingConfig } from '@/config/marketing'; import { routing } from '@/i18n/routing'; import { cn } from "@/lib/utils"; import { GeistMono } from "geist/font/mono"; diff --git a/src/components/blog/all-posts-button.tsx b/src/components/blog/all-posts-button.tsx index 5565247..1845b10 100644 --- a/src/components/blog/all-posts-button.tsx +++ b/src/components/blog/all-posts-button.tsx @@ -1,10 +1,12 @@ "use client"; import { Button } from "@/components/ui/button"; -import { ArrowLeftIcon } from "lucide-react"; import { LocaleLink } from "@/i18n/navigation"; +import { ArrowLeftIcon } from "lucide-react"; +import { useTranslations } from "next-intl"; export default function AllPostsButton() { + const t = useTranslations("BlogPage"); return ( ); diff --git a/src/components/blog/blog-category-list-mobile.tsx b/src/components/blog/blog-category-list-mobile.tsx index 2bdbec1..5da9aa8 100644 --- a/src/components/blog/blog-category-list-mobile.tsx +++ b/src/components/blog/blog-category-list-mobile.tsx @@ -1,12 +1,19 @@ "use client"; +import FilterItemMobile from "@/components/shared/filter-item-mobile"; +import { + Drawer, + DrawerContent, + DrawerOverlay, + DrawerPortal, + DrawerTitle, + DrawerTrigger, +} from "@/components/ui/drawer"; import { Category } from "content-collections"; import { LayoutListIcon } from "lucide-react"; +import { useTranslations } from "next-intl"; import { useParams } from "next/navigation"; import { useState } from "react"; -import { Drawer } from "vaul"; -import FilterItemMobile from "@/components/shared/filter-item-mobile"; -import { useTranslations } from "next-intl"; export type BlogCategoryListMobileProps = { categoryList: Category[]; @@ -27,8 +34,8 @@ export function BlogCategoryListMobile({ }; return ( - - + setOpen(true)} className="flex items-center w-full p-3 border-y text-foreground/90" > @@ -41,14 +48,11 @@ export function BlogCategoryListMobile({ {selectedCategory?.name ? `${selectedCategory?.name}` : t("all")}
- - - - - {t("categories")} + + + + + {t("categories")}
@@ -71,9 +75,8 @@ export function BlogCategoryListMobile({ /> ))} - - - - + + + ); } diff --git a/src/components/blog/blog-post.tsx b/src/components/blog/blog-post.tsx deleted file mode 100644 index e4f535a..0000000 --- a/src/components/blog/blog-post.tsx +++ /dev/null @@ -1,78 +0,0 @@ -import * as React from 'react'; -import Link from 'next/link'; -import { format } from 'date-fns'; -import { Mdx } from '@/components/marketing/blog/mdx-component'; -import { Avatar, AvatarFallback, AvatarImage } from '@/components/ui/avatar'; -import { Separator } from '@/components/ui/separator'; -import { getInitials } from '@/lib/utils'; -import { Post } from 'content-collections'; - -type BlogPostProps = { - post: Post; -}; - -export function BlogPost({ post }: BlogPostProps): React.JSX.Element { - console.log(post); - console.log(post.author); - return ( -
-
-
- - - ← - - All posts - -
-
- - {post.categories.map((c) => c?.name).join(', ')} - - - - -
-

- {post.title} -

-

{post.description}

-
-
- - - - {getInitials(post.author?.name ?? '')} - - - {post.author?.name ?? ''} -
-
{estimateReadingTime(post.body.raw)}
-
-
-
-
- -
- -
-
- ); -} - -function estimateReadingTime( - text: string, - wordsPerMinute: number = 250 -): string { - const words = text.trim().split(/\s+/).length; - const minutes = Math.ceil(words / wordsPerMinute); - return minutes === 1 ? '1 minute read' : `${minutes} minutes read`; -} diff --git a/src/components/blog/blog-posts.tsx b/src/components/blog/blog-posts.tsx deleted file mode 100644 index c714b5c..0000000 --- a/src/components/blog/blog-posts.tsx +++ /dev/null @@ -1,78 +0,0 @@ -import * as React from 'react'; -import Link from 'next/link'; -import { allPosts } from 'content-collections'; -import { format, isBefore } from 'date-fns'; -import { ArrowRightIcon } from 'lucide-react'; -import { GridSection } from '@/components/marketing/fragments/grid-section'; -import { SiteHeading } from '@/components/marketing/fragments/site-heading'; -import { Avatar, AvatarFallback, AvatarImage } from '@/components/ui/avatar'; -import { FillRemainingSpace } from '@/components/fill-remaining-space'; -import { getBaseUrl } from '@/lib/urls/get-base-url'; -import { getInitials } from '@/lib/utils'; -import { useLocale } from 'next-intl'; - -export function BlogPosts(): React.JSX.Element { - const locale = useLocale(); - - // Filter posts by current locale - const localizedPosts = allPosts - .filter(post => post.published && post.locale === locale) - .slice() - .sort((a, b) => (isBefore(a.date, b.date) ? 1 : -1)); - - return ( - -
- -
- {localizedPosts.map((post, index) => ( - -
- {post.categories.map((c) => c?.name).join(', ')} - -
-

- {post.title} -

-

- {post.description} -

- -
-
- - - - {getInitials(post.author?.name ?? '')} - - - {post.author?.name ?? ''} -
-
- Read more - -
-
- - ))} -
-
-
- ); -} diff --git a/src/components/marketing/navbar-mobile.tsx b/src/components/layout/navbar-mobile.tsx similarity index 72% rename from src/components/marketing/navbar-mobile.tsx rename to src/components/layout/navbar-mobile.tsx index 3be8fd2..88a9c1f 100644 --- a/src/components/marketing/navbar-mobile.tsx +++ b/src/components/layout/navbar-mobile.tsx @@ -1,7 +1,7 @@ 'use client'; import { Logo } from '@/components/logo'; -import { DOCS_LINKS, MENU_LINKS } from '@/components/marketing/marketing-links'; +import { MENU_LINKS } from '@/config/marketing-links'; import { Button, buttonVariants } from '@/components/ui/button'; import { Collapsible, @@ -27,7 +27,6 @@ export function NavbarMobile({ }: React.HTMLAttributes) { const [open, setOpen] = React.useState(false); const pathname = usePathname(); - const isDocs = pathname.startsWith('/docs'); React.useEffect(() => { const handleRouteChangeStart = () => { @@ -90,11 +89,7 @@ export function NavbarMobile({ {open && ( - {isDocs ? ( - - ) : ( - - )} + )} @@ -238,77 +233,3 @@ function MainMobileMenu({ onLinkClicked }: MainMobileMenuProps) {
); } - -interface DocsMobileMenuProps { - onLinkClicked: () => void; -}; - -function DocsMobileMenu({ - onLinkClicked -}: DocsMobileMenuProps): React.JSX.Element { - const [expanded, setExpanded] = React.useState>({}); - return ( -
-
-
    - {DOCS_LINKS.map((item) => ( -
  • - - setExpanded((prev) => ({ - ...prev, - [item.title.toLowerCase()]: isOpen - })) - } - > - - - - -
      - {item.items.map((subItem) => ( -
    • - - {subItem.title} - -
    • - ))} -
    -
    -
    -
  • - ))} -
-
- - -
-
-
- ); -} diff --git a/src/components/layout/navbar.tsx b/src/components/layout/navbar.tsx index cd3e823..3c7dcc4 100644 --- a/src/components/layout/navbar.tsx +++ b/src/components/layout/navbar.tsx @@ -1,11 +1,13 @@ -"use client"; +'use client'; -import { LoginWrapper } from "@/components/auth/login-button"; -import Container from "@/components/container"; -import { Icons } from "@/components/icons/icons"; -import { UserButton } from "@/components/layout/user-button"; -import { Logo } from "@/components/logo"; -import { Button } from "@/components/ui/button"; +import { LoginWrapper } from '@/components/auth/login-button'; +import Container from '@/components/container'; +import { ThemeSwitcher } from '@/components/layout/theme-switcher'; +import { UserButton } from '@/components/layout/user-button'; +import { Logo } from '@/components/logo'; +import { MENU_LINKS } from '@/config/marketing-links'; +import { NavbarMobile } from '@/components/layout/navbar-mobile'; +import { Button } from '@/components/ui/button'; import { NavigationMenu, NavigationMenuContent, @@ -13,274 +15,180 @@ import { NavigationMenuLink, NavigationMenuList, NavigationMenuTrigger, - navigationMenuTriggerStyle, -} from "@/components/ui/navigation-menu"; -import { Sheet, SheetContent, SheetHeader, SheetTitle, SheetTrigger } from "@/components/ui/sheet"; -import { siteConfig } from "@/config/site"; + navigationMenuTriggerStyle +} from '@/components/ui/navigation-menu'; +import { siteConfig } from '@/config/site'; import { useScroll } from "@/hooks/use-scroll"; -import { authClient } from "@/lib/auth-client"; -import { cn } from "@/lib/utils"; -import type { DashboardConfig, MarketingConfig, NestedNavItem } from "@/types"; -import { Accordion, AccordionContent, AccordionItem, AccordionTrigger } from "@radix-ui/react-accordion"; -import { MenuIcon } from "lucide-react"; -import Link from "next/link"; -import { usePathname } from "next/navigation"; -import { useEffect, useState } from "react"; +import { authClient } from '@/lib/auth-client'; +import { cn } from '@/lib/utils'; +import { Routes } from '@/routes'; +import { MarketingConfig } from '@/types'; +import Link from 'next/link'; +import { usePathname } from 'next/navigation'; +import { ArrowUpRightIcon } from 'lucide-react'; +import LocaleSelector from '@/components/layout/locale-selector'; +import { LocaleLink } from '@/i18n/navigation'; interface NavBarProps { scroll?: boolean; - config: DashboardConfig | MarketingConfig; + config: MarketingConfig; } -export function Navbar({ scroll = false, config }: NavBarProps) { +const customNavigationMenuTriggerStyle = cn( + navigationMenuTriggerStyle(), + "bg-transparent hover:bg-transparent hover:text-primary focus:bg-transparent focus:text-primary data-[active]:bg-transparent data-[active]:text-primary data-[state=open]:bg-transparent data-[state=open]:text-primary relative data-[active]:font-bold dark:text-gray-400 dark:hover:text-gray-300 dark:data-[active]:text-white" +); + +export function Navbar({ scroll, config }: NavBarProps) { const scrolled = useScroll(50); const { data: session, error } = authClient.useSession(); const user = session?.user; - console.log(`Navbar, user:`, user); + // console.log(`Navbar, user:`, user); const pathname = usePathname(); - // console.log(`Navbar, pathname: ${pathname}`); - const menus = config.menus; - - const isMenuActive = (href: string) => { - if (href === "/") { - return pathname === "/"; - } - // console.log(`Navbar, href: ${href}, pathname: ${pathname}`); - return pathname.startsWith(href); - }; - - const [open, setOpen] = useState(false); - // prevent body scroll when modal is open - useEffect(() => { - if (open) { - document.body.style.overflow = "hidden"; - } else { - document.body.style.overflow = "auto"; - } - }, [open]); return ( -
- {/* Desktop View */} -
- - {/* navbar left show logo and links */} -
- {/* logo */} - +
+ + {/* desktop navbar */} +
- - {/* Mobile View */} -
-
- {/* mobile navbar left show menu icon when closed & show sheet when menu is open */} -
- - - - - - - - setOpen(false)} - > - - {siteConfig.name} - - - -
- + +
-
-
+ Log in + + - {/* logo */} - setOpen(false)} - > - - - {siteConfig.name} - -
- - {/* mobile navbar right show sign in or user button */} -
- {user ? ( -
- -
- ) : ( - - +
)} + + +
-
- - + + + {/* mobile navbar */} + + + ); } - -const renderMenuItem = (item: NestedNavItem) => { - if (item.items) { - return ( - - - {item.title} - - - - - - ); - } - - return ( - - - {item.title} - - - ); -}; - -const renderMobileMenuItem = (item: NestedNavItem) => { - console.log(`renderMobileMenuItem, item:`, item, `, items:`, item.items); - if (item.items) { - return ( - - - {item.title} - - - {item.items.map((subItem) => { - const CustomMenuIcon = Icons[subItem.icon || "arrowRight"]; - return ( - - {subItem.icon && } -
-
{subItem.title}
-
- - ); - })} -
-
- ); - } - - const CustomMenuIcon = Icons[item.icon || "arrowRight"]; - return ( - - {item.icon && } - {item.title} - - ); -}; diff --git a/src/components/legacy/navbar.tsx b/src/components/legacy/navbar.tsx new file mode 100644 index 0000000..cd3e823 --- /dev/null +++ b/src/components/legacy/navbar.tsx @@ -0,0 +1,286 @@ +"use client"; + +import { LoginWrapper } from "@/components/auth/login-button"; +import Container from "@/components/container"; +import { Icons } from "@/components/icons/icons"; +import { UserButton } from "@/components/layout/user-button"; +import { Logo } from "@/components/logo"; +import { Button } from "@/components/ui/button"; +import { + NavigationMenu, + NavigationMenuContent, + NavigationMenuItem, + NavigationMenuLink, + NavigationMenuList, + NavigationMenuTrigger, + navigationMenuTriggerStyle, +} from "@/components/ui/navigation-menu"; +import { Sheet, SheetContent, SheetHeader, SheetTitle, SheetTrigger } from "@/components/ui/sheet"; +import { siteConfig } from "@/config/site"; +import { useScroll } from "@/hooks/use-scroll"; +import { authClient } from "@/lib/auth-client"; +import { cn } from "@/lib/utils"; +import type { DashboardConfig, MarketingConfig, NestedNavItem } from "@/types"; +import { Accordion, AccordionContent, AccordionItem, AccordionTrigger } from "@radix-ui/react-accordion"; +import { MenuIcon } from "lucide-react"; +import Link from "next/link"; +import { usePathname } from "next/navigation"; +import { useEffect, useState } from "react"; + +interface NavBarProps { + scroll?: boolean; + config: DashboardConfig | MarketingConfig; +} + +export function Navbar({ scroll = false, config }: NavBarProps) { + const scrolled = useScroll(50); + const { data: session, error } = authClient.useSession(); + const user = session?.user; + console.log(`Navbar, user:`, user); + + const pathname = usePathname(); + // console.log(`Navbar, pathname: ${pathname}`); + const menus = config.menus; + + const isMenuActive = (href: string) => { + if (href === "/") { + return pathname === "/"; + } + // console.log(`Navbar, href: ${href}, pathname: ${pathname}`); + return pathname.startsWith(href); + }; + + const [open, setOpen] = useState(false); + // prevent body scroll when modal is open + useEffect(() => { + if (open) { + document.body.style.overflow = "hidden"; + } else { + document.body.style.overflow = "auto"; + } + }, [open]); + + return ( +
+ {/* Desktop View */} +
+ + {/* navbar left show logo and links */} +
+ {/* logo */} + + + + {siteConfig.name} + +
+ + {/* links */} +
+ {menus && menus.length > 0 ? ( + + + {menus.map((item) => renderMenuItem(item))} + + + ) : null} +
+ + {/* navbar right show sign in or account */} +
+ {user ? ( +
+ +
+ ) : ( + + + + )} +
+
+
+ + {/* Mobile View */} +
+
+ {/* mobile navbar left show menu icon when closed & show sheet when menu is open */} +
+ + + + + + + + setOpen(false)} + > + + {siteConfig.name} + + + +
+ + {menus.map((item) => renderMobileMenuItem(item))} + +
+
+
+ + {/* logo */} + setOpen(false)} + > + + + {siteConfig.name} + +
+ + {/* mobile navbar right show sign in or user button */} +
+ {user ? ( +
+ +
+ ) : ( + + + + )} +
+
+
+
+ ); +} + +const renderMenuItem = (item: NestedNavItem) => { + if (item.items) { + return ( + + + {item.title} + + + + + + ); + } + + return ( + + + {item.title} + + + ); +}; + +const renderMobileMenuItem = (item: NestedNavItem) => { + console.log(`renderMobileMenuItem, item:`, item, `, items:`, item.items); + if (item.items) { + return ( + + + {item.title} + + + {item.items.map((subItem) => { + const CustomMenuIcon = Icons[subItem.icon || "arrowRight"]; + return ( + + {subItem.icon && } +
+
{subItem.title}
+
+ + ); + })} +
+
+ ); + } + + const CustomMenuIcon = Icons[item.icon || "arrowRight"]; + return ( + + {item.icon && } + {item.title} + + ); +}; diff --git a/src/components/marketing/blog/callout.tsx b/src/components/marketing/blog/callout.tsx deleted file mode 100644 index 3e1fa9c..0000000 --- a/src/components/marketing/blog/callout.tsx +++ /dev/null @@ -1,30 +0,0 @@ -import * as React from 'react'; -import { ComponentProps } from 'react'; -import { - Alert, - AlertDescription, - AlertTitle -} from '@/components/ui/alert'; - -type CalloutProps = ComponentProps & { - icon?: string; - title?: string; -}; - -/** - * TODO: update - */ -export function Callout({ - title, - children, - icon, - ...props -}: CalloutProps): React.JSX.Element { - return ( - - {icon && {icon}} - {title && {title}} - {children} - - ); -} diff --git a/src/components/marketing/fragments/grid-section.tsx b/src/components/marketing/fragments/grid-section.tsx deleted file mode 100644 index a5c29e3..0000000 --- a/src/components/marketing/fragments/grid-section.tsx +++ /dev/null @@ -1,39 +0,0 @@ -import * as React from 'react'; -import { cn } from '@/lib/utils'; - -export type GridSectionProps = React.HtmlHTMLAttributes & { - hideVerticalGridLines?: boolean; - hideBottomGridLine?: boolean; - containerProps?: React.HtmlHTMLAttributes; -}; - -/** - * TODO: remove - */ -export function GridSection({ - children, - hideVerticalGridLines, - hideBottomGridLine, - containerProps: { className = '', ...containerProps } = {}, - ...other -}: GridSectionProps): React.JSX.Element { - return ( -
-
-
- {!hideVerticalGridLines && ( - <> -
-
- - )} - {children} -
-
- {!hideBottomGridLine &&
} -
- ); -} diff --git a/src/components/marketing/fragments/site-heading.tsx b/src/components/marketing/fragments/site-heading.tsx deleted file mode 100644 index d34fba5..0000000 --- a/src/components/marketing/fragments/site-heading.tsx +++ /dev/null @@ -1,38 +0,0 @@ -import * as React from 'react'; -import { Badge } from '@/components/ui/badge'; - -export type SiteHeadingProps = { - badge?: React.ReactNode; - title?: React.ReactNode; - description?: React.ReactNode; -}; - -/** - * TODO: remove - */ -export function SiteHeading({ - badge, - title, - description -}: SiteHeadingProps): React.JSX.Element { - return ( -
- {badge && ( - - {badge} - - )} - {title && ( -

{title}

- )} - {description && ( -

- {description} -

- )} -
- ); -} diff --git a/src/components/marketing/navbar.tsx b/src/components/marketing/navbar.tsx deleted file mode 100644 index 387791c..0000000 --- a/src/components/marketing/navbar.tsx +++ /dev/null @@ -1,194 +0,0 @@ -'use client'; - -import { LoginWrapper } from '@/components/auth/login-button'; -import Container from '@/components/container'; -import { ThemeSwitcher } from '@/components/layout/theme-switcher'; -import { UserButton } from '@/components/layout/user-button'; -import { Logo } from '@/components/logo'; -import { MENU_LINKS } from '@/components/marketing/marketing-links'; -import { NavbarMobile } from '@/components/marketing/navbar-mobile'; -import { Button } from '@/components/ui/button'; -import { - NavigationMenu, - NavigationMenuContent, - NavigationMenuItem, - NavigationMenuLink, - NavigationMenuList, - NavigationMenuTrigger, - navigationMenuTriggerStyle -} from '@/components/ui/navigation-menu'; -import { siteConfig } from '@/config/site'; -import { useScroll } from "@/hooks/use-scroll"; -import { authClient } from '@/lib/auth-client'; -import { cn } from '@/lib/utils'; -import { Routes } from '@/routes'; -import { MarketingConfig } from '@/types'; -import Link from 'next/link'; -import { usePathname } from 'next/navigation'; -import { ArrowUpRightIcon } from 'lucide-react'; -import LocaleSelector from '@/components/layout/locale-selector'; -import { LocaleLink } from '@/i18n/navigation'; - -interface NavBarProps { - scroll?: boolean; - config: MarketingConfig; -} - -const customNavigationMenuTriggerStyle = cn( - navigationMenuTriggerStyle(), - "bg-transparent hover:bg-transparent hover:text-primary focus:bg-transparent focus:text-primary data-[active]:bg-transparent data-[active]:text-primary data-[state=open]:bg-transparent data-[state=open]:text-primary relative data-[active]:font-bold dark:text-gray-400 dark:hover:text-gray-300 dark:data-[active]:text-white" -); - -export function Navbar({ scroll, config }: NavBarProps) { - const scrolled = useScroll(50); - const { data: session, error } = authClient.useSession(); - const user = session?.user; - // console.log(`Navbar, user:`, user); - - const pathname = usePathname(); - - return ( -
- - {/* desktop navbar */} - - - {/* mobile navbar */} - - -
- ); -} diff --git a/src/components/shared/callout-custom.tsx b/src/components/shared/callout-custom.tsx new file mode 100644 index 0000000..8d4a2bc --- /dev/null +++ b/src/components/shared/callout-custom.tsx @@ -0,0 +1,86 @@ +import { + AlertTriangle, + Ban, + CircleAlert, + CircleCheckBig, + FileText, + Info, + Lightbulb, +} from "lucide-react"; + +import { cn } from "@/lib/utils"; + +interface CalloutProps { + twClass?: string; + children?: React.ReactNode; + type?: keyof typeof dataCallout; +} + +const dataCallout = { + default: { + icon: Info, + classes: + "border-zinc-200 bg-gray-50 text-zinc-900 dark:bg-zinc-800 dark:text-zinc-200", + }, + danger: { + icon: CircleAlert, + classes: + "border-red-200 bg-red-50 text-red-900 dark:bg-red-950 dark:text-red-200", + }, + error: { + icon: Ban, + classes: + "border-red-200 bg-red-50 text-red-900 dark:bg-red-950 dark:text-red-200", + }, + idea: { + icon: Lightbulb, + classes: + "border-blue-200 bg-blue-50 text-blue-800 dark:bg-blue-950 dark:text-blue-200", + }, + info: { + icon: Info, + classes: + "border-blue-200 bg-blue-50 text-blue-800 dark:bg-blue-950 dark:text-blue-200", + }, + note: { + icon: FileText, + classes: + "border-blue-200 bg-blue-50 text-blue-800 dark:bg-blue-950 dark:text-blue-200", + }, + success: { + icon: CircleCheckBig, + classes: + "border-green-200 bg-green-50 text-green-800 dark:bg-green-400/20 dark:text-green-300", + }, + warning: { + icon: AlertTriangle, + classes: + "border-orange-200 bg-orange-50 text-orange-800 dark:bg-orange-400/20 dark:text-orange-300", + }, +}; + +export function Callout({ + children, + twClass, + type = "default", + ...props +}: CalloutProps) { + const { icon: Icon, classes } = dataCallout[type]; + return ( +
+ {/*
+
+ +
+
{children}
+
*/} +
+ ); +} diff --git a/src/components/shared/callout.tsx b/src/components/shared/callout.tsx index 8d4a2bc..3e1fa9c 100644 --- a/src/components/shared/callout.tsx +++ b/src/components/shared/callout.tsx @@ -1,86 +1,30 @@ +import * as React from 'react'; +import { ComponentProps } from 'react'; import { - AlertTriangle, - Ban, - CircleAlert, - CircleCheckBig, - FileText, - Info, - Lightbulb, -} from "lucide-react"; + Alert, + AlertDescription, + AlertTitle +} from '@/components/ui/alert'; -import { cn } from "@/lib/utils"; - -interface CalloutProps { - twClass?: string; - children?: React.ReactNode; - type?: keyof typeof dataCallout; -} - -const dataCallout = { - default: { - icon: Info, - classes: - "border-zinc-200 bg-gray-50 text-zinc-900 dark:bg-zinc-800 dark:text-zinc-200", - }, - danger: { - icon: CircleAlert, - classes: - "border-red-200 bg-red-50 text-red-900 dark:bg-red-950 dark:text-red-200", - }, - error: { - icon: Ban, - classes: - "border-red-200 bg-red-50 text-red-900 dark:bg-red-950 dark:text-red-200", - }, - idea: { - icon: Lightbulb, - classes: - "border-blue-200 bg-blue-50 text-blue-800 dark:bg-blue-950 dark:text-blue-200", - }, - info: { - icon: Info, - classes: - "border-blue-200 bg-blue-50 text-blue-800 dark:bg-blue-950 dark:text-blue-200", - }, - note: { - icon: FileText, - classes: - "border-blue-200 bg-blue-50 text-blue-800 dark:bg-blue-950 dark:text-blue-200", - }, - success: { - icon: CircleCheckBig, - classes: - "border-green-200 bg-green-50 text-green-800 dark:bg-green-400/20 dark:text-green-300", - }, - warning: { - icon: AlertTriangle, - classes: - "border-orange-200 bg-orange-50 text-orange-800 dark:bg-orange-400/20 dark:text-orange-300", - }, +type CalloutProps = ComponentProps & { + icon?: string; + title?: string; }; +/** + * TODO: update + */ export function Callout({ + title, children, - twClass, - type = "default", + icon, ...props -}: CalloutProps) { - const { icon: Icon, classes } = dataCallout[type]; +}: CalloutProps): React.JSX.Element { return ( -
- {/*
-
- -
-
{children}
-
*/} -
+ + {icon && {icon}} + {title && {title}} + {children} + ); } diff --git a/src/components/marketing/blog/mdx-component.tsx b/src/components/shared/mdx-component.tsx similarity index 99% rename from src/components/marketing/blog/mdx-component.tsx rename to src/components/shared/mdx-component.tsx index ec65042..18948e9 100644 --- a/src/components/marketing/blog/mdx-component.tsx +++ b/src/components/shared/mdx-component.tsx @@ -4,7 +4,7 @@ import * as React from 'react'; import Image from 'next/image'; import Link from 'next/link'; import { useMDXComponent } from '@content-collections/mdx/react'; -import { Callout } from '@/components/marketing/blog/callout'; +import { Callout } from '@/components/shared/callout'; import { Accordion, AccordionContent, diff --git a/src/components/marketing/marketing-links.tsx b/src/config/marketing-links.tsx similarity index 84% rename from src/components/marketing/marketing-links.tsx rename to src/config/marketing-links.tsx index afa2e42..8178513 100644 --- a/src/components/marketing/marketing-links.tsx +++ b/src/config/marketing-links.tsx @@ -1,20 +1,16 @@ -import * as React from 'react'; -import { CubeIcon, PaperPlaneIcon } from '@radix-ui/react-icons'; -import { - BookIcon, - BookOpenIcon, - CircuitBoardIcon, - CuboidIcon, - FileBarChartIcon, - LayoutIcon, - PlayIcon -} from 'lucide-react'; -import { Routes } from '@/routes'; import { FacebookIcon } from '@/components/icons/facebook'; import { InstagramIcon } from '@/components/icons/instagram'; import { LinkedInIcon } from '@/components/icons/linkedin'; import { TikTokIcon } from '@/components/icons/tiktok'; import { XTwitterIcon } from '@/components/icons/x'; +import { Routes } from '@/routes'; +import { CubeIcon, PaperPlaneIcon } from '@radix-ui/react-icons'; +import { + BookOpenIcon, + FileBarChartIcon, + LayoutIcon, + PlayIcon +} from 'lucide-react'; export const MENU_LINKS = [ { @@ -32,11 +28,6 @@ export const MENU_LINKS = [ href: Routes.Blog, external: false }, - // { - // title: 'Docs', - // href: Routes.Docs, - // external: false - // }, { title: 'AI', items: [ @@ -162,33 +153,3 @@ export const SOCIAL_LINKS = [ icon: } ]; - -export const DOCS_LINKS = [ - { - title: 'Getting Started', - icon: , - items: [ - { - title: 'Introduction', - href: '/docs', - items: [] - }, - { - title: 'Dependencies', - href: '/docs/dependencies', - items: [] - } - ] - }, - { - title: 'Guides', - icon: , - items: [ - { - title: 'Using MDX', - href: '/docs/using-mdx', - items: [] - } - ] - } -]; diff --git a/src/lib/utils.ts b/src/lib/utils.ts index b78b909..aa86fc5 100644 --- a/src/lib/utils.ts +++ b/src/lib/utils.ts @@ -72,10 +72,26 @@ export function getLocaleDate(input: string | number): string { * @param request - The request to get the locale from * @returns The locale from the request or the default locale */ -export const getLocaleFromRequest = (request?: Request) => { +export function getLocaleFromRequest(request?: Request): Locale { const cookies = parseCookies(request?.headers.get("cookie") ?? ""); return ( (cookies[LOCALE_COOKIE_NAME] as Locale) ?? routing.defaultLocale ); -}; \ No newline at end of file +}; + +/** + * Estimates the reading time of a text + * + * @param text - The text to estimate the reading time of + * @param wordsPerMinute - The number of words per minute to use for the estimate + * @returns The estimated reading time + */ +export function estimateReadingTime( + text: string, + wordsPerMinute: number = 250 +): string { + const words = text.trim().split(/\s+/).length; + const minutes = Math.ceil(words / wordsPerMinute); + return minutes === 1 ? '1 minute read' : `${minutes} minutes read`; +} \ No newline at end of file