Some changes ported from the work on the makerkit.dev website related… (#89)

* Some changes ported from the work on the makerkit.dev website related to the marketing sections of the kit, such as documentation
* Added slight background hue to make darker theme better looking
* Support more complex configurations for documentation navigations.
* Do not fetch content from Keystatic when non-needed
* Add cursor pointers in dropdown
* Updated packages
This commit is contained in:
Giancarlo Buomprisco
2024-12-09 05:58:17 +01:00
committed by GitHub
parent a682b991f3
commit 079a8f857a
44 changed files with 762 additions and 456 deletions

View File

@@ -3,15 +3,15 @@
} }
.HTML h1 { .HTML h1 {
@apply mt-14 text-4xl font-semibold font-heading tracking-tight ; @apply mt-14 text-4xl font-semibold font-heading tracking-tight;
} }
.HTML h2 { .HTML h2 {
@apply mb-4 mt-12 font-semibold text-2xl font-heading tracking-tight; @apply mb-6 mt-12 font-semibold text-2xl font-heading tracking-tight;
} }
.HTML h3 { .HTML h3 {
@apply mt-10 text-xl font-semibold font-heading tracking-tight; @apply mt-12 text-xl font-semibold font-heading tracking-tight;
} }
.HTML h4 { .HTML h4 {
@@ -41,11 +41,11 @@ For more info: https://github.com/tailwindlabs/tailwindcss/issues/3258#issuecomm
} }
.HTML p { .HTML p {
@apply mb-4 mt-2 text-base leading-7; @apply mb-6 mt-4 text-base leading-7 text-muted-foreground;
} }
.HTML li { .HTML li {
@apply relative my-1.5 text-base leading-7; @apply relative my-1.5 text-base leading-7 text-muted-foreground;
} }
.HTML ul > li:before { .HTML ul > li:before {
@@ -55,7 +55,7 @@ For more info: https://github.com/tailwindlabs/tailwindcss/issues/3258#issuecomm
} }
.HTML ol > li:before { .HTML ol > li:before {
@apply inline-flex font-medium; @apply inline-flex font-medium text-muted-foreground;
content: counters(counts, '.') '. '; content: counters(counts, '.') '. ';
font-feature-settings: 'tnum'; font-feature-settings: 'tnum';
@@ -63,7 +63,7 @@ For more info: https://github.com/tailwindlabs/tailwindcss/issues/3258#issuecomm
.HTML b, .HTML b,
.HTML strong { .HTML strong {
@apply font-bold; @apply font-semibold text-secondary-foreground;
} }
:global(.dark) .HTML b, :global(.dark) .HTML b,
@@ -92,10 +92,35 @@ For more info: https://github.com/tailwindlabs/tailwindcss/issues/3258#issuecomm
font-feature-settings: 'tnum'; font-feature-settings: 'tnum';
} }
.HTML p > code, .HTML li > code {
@apply p-0.5 text-sm font-semibold bg-muted/50 border font-mono text-secondary-foreground;
}
.HTML blockquote { .HTML blockquote {
@apply my-4 border-l-8 border border-primary px-6 py-4 text-lg font-medium text-muted-foreground; @apply my-4 border-l-8 border border-primary px-6 py-4 text-lg font-medium text-muted-foreground;
} }
.HTML pre { .HTML a {
@apply my-6 text-sm text-current border p-6 rounded-lg overflow-x-scroll; @apply border-b-black border-b hover:border-b-2 pb-0.5 text-secondary-foreground font-semibold;
} }
:global(.dark) .HTML a {
@apply border-yellow-300;
}
.HTML hr {
@apply mt-8 mb-6 border-border;
}
.HTML [role='alert'] {
@apply py-4 m-0 my-8;
}
.HTML [role='alert'] * {
color: inherit;
@apply m-0 p-0 text-sm;
}
.HTML [role='alert'] h5 {
color: inherit;
}

View File

@@ -37,6 +37,7 @@ const getContentItems = cache(
limit, limit,
offset, offset,
language, language,
content: false,
sortBy: 'publishedAt', sortBy: 'publishedAt',
sortDirection: 'desc', sortDirection: 'desc',
}); });

View File

@@ -61,13 +61,15 @@ async function DocumentationPage({ params }: DocumentationPageProps) {
); );
return ( return (
<div className={'flex flex-1 flex-col space-y-4'}> <div className={'flex flex-1 flex-col space-y-4 overflow-y-hidden'}>
<div className={'flex'}> <div className={'flex overflow-y-hidden'}>
<article className={cn(styles.HTML, 'container space-y-12')}> <article
className={cn(styles.HTML, 'container space-y-12 overflow-y-auto')}
>
<section className={'flex flex-col space-y-4 pt-6'}> <section className={'flex flex-col space-y-4 pt-6'}>
<h1 className={'!my-0'}>{page.title}</h1> <h1 className={'!my-0'}>{page.title}</h1>
<h2 className={'!mb-0 !font-normal text-muted-foreground'}> <h2 className={'!mb-0 !font-normal !text-muted-foreground'}>
{description} {description}
</h2> </h2>
</section> </section>

View File

@@ -1,13 +1,20 @@
'use client'; 'use client';
import { useRef } from 'react';
import Link from 'next/link'; import Link from 'next/link';
import { usePathname } from 'next/navigation'; import { usePathname } from 'next/navigation';
import { SidebarMenuButton, SidebarMenuItem } from '@kit/ui/shadcn-sidebar'; import { SidebarMenuButton, SidebarMenuItem } from '@kit/ui/shadcn-sidebar';
import { cn, isRouteActive } from '@kit/ui/utils'; import { cn, isRouteActive } from '@kit/ui/utils';
export function DocsNavLink({ label, url }: { label: string; url: string }) { export function DocsNavLink({
label,
url,
children,
}: React.PropsWithChildren<{ label: string; url: string }>) {
const currentPath = usePathname(); const currentPath = usePathname();
const ref = useRef<HTMLElement>(null);
const isCurrent = isRouteActive(url, currentPath, true); const isCurrent = isRouteActive(url, currentPath, true);
return ( return (
@@ -15,10 +22,16 @@ export function DocsNavLink({ label, url }: { label: string; url: string }) {
<SidebarMenuButton <SidebarMenuButton
asChild asChild
isActive={isCurrent} isActive={isCurrent}
className={cn('border-l-3 transition-background !font-normal')} className={cn('border-l-3 transition-background !font-normal', {
'font-bold text-secondary-foreground': isCurrent,
})}
> >
<Link href={url}> <Link href={url}>
<span className="block max-w-full truncate">{label}</span> <span ref={ref} className="block max-w-full truncate">
{label}
</span>
{children}
</Link> </Link>
</SidebarMenuButton> </SidebarMenuButton>
</SidebarMenuItem> </SidebarMenuItem>

View File

@@ -0,0 +1,30 @@
'use client';
import { usePathname } from 'next/navigation';
import { Cms } from '@kit/cms';
import { Collapsible } from '@kit/ui/collapsible';
import { isRouteActive } from '@kit/ui/utils';
export function DocsNavigationCollapsible(
props: React.PropsWithChildren<{
node: Cms.ContentItem;
prefix: string;
}>,
) {
const currentPath = usePathname();
const prefix = props.prefix;
const isChildActive = props.node.children.some((child) =>
isRouteActive(prefix + '/' + child.url, currentPath, false),
);
return (
<Collapsible
className={'group/collapsible'}
defaultOpen={isChildActive ? true : !props.node.collapsed}
>
{props.children}
</Collapsible>
);
}

View File

@@ -1,66 +1,137 @@
import { ChevronDown } from 'lucide-react';
import { Cms } from '@kit/cms'; import { Cms } from '@kit/cms';
import { CollapsibleContent, CollapsibleTrigger } from '@kit/ui/collapsible';
import { import {
Sidebar, Sidebar,
SidebarGroup, SidebarGroup,
SidebarGroupContent, SidebarGroupContent,
SidebarMenu, SidebarMenu,
SidebarMenuButton,
SidebarMenuItem,
SidebarMenuSub, SidebarMenuSub,
} from '@kit/ui/shadcn-sidebar'; } from '@kit/ui/shadcn-sidebar';
import { DocsNavLink } from '~/(marketing)/docs/_components/docs-nav-link'; import { DocsNavLink } from '~/(marketing)/docs/_components/docs-nav-link';
import { DocsNavigationCollapsible } from '~/(marketing)/docs/_components/docs-navigation-collapsible';
import { FloatingDocumentationNavigation } from './floating-docs-navigation'; import { FloatingDocumentationNavigation } from './floating-docs-navigation';
function Node({ node, level }: { node: Cms.ContentItem; level: number }) { function Node({
const pathPrefix = `/docs`; node,
const url = `${pathPrefix}/${node.slug}`; level,
prefix,
}: {
node: Cms.ContentItem;
level: number;
prefix: string;
}) {
const url = `${prefix}/${node.slug}`;
const label = node.label ? node.label : node.title;
const Container = (props: React.PropsWithChildren) => {
if (node.collapsible) {
return (
<DocsNavigationCollapsible node={node} prefix={prefix}>
{props.children}
</DocsNavigationCollapsible>
);
}
return props.children;
};
const ContentContainer = (props: React.PropsWithChildren) => {
if (node.collapsible) {
return <CollapsibleContent>{props.children}</CollapsibleContent>;
}
return props.children;
};
const Trigger = () => {
if (node.collapsible) {
return (
<CollapsibleTrigger asChild>
<SidebarMenuItem>
<SidebarMenuButton>
{label}
<ChevronDown className="ml-auto transition-transform group-data-[state=open]/collapsible:rotate-180" />
</SidebarMenuButton>
</SidebarMenuItem>
</CollapsibleTrigger>
);
}
return <DocsNavLink label={label} url={url} />;
};
return ( return (
<> <Container>
<DocsNavLink label={node.title} url={url} /> <Trigger />
{(node.children ?? []).length > 0 && ( <ContentContainer>
<Tree pages={node.children ?? []} level={level + 1} /> <Tree pages={node.children ?? []} level={level + 1} prefix={prefix} />
)} </ContentContainer>
</> </Container>
); );
} }
function Tree({ pages, level }: { pages: Cms.ContentItem[]; level: number }) { function Tree({
pages,
level,
prefix,
}: {
pages: Cms.ContentItem[];
level: number;
prefix: string;
}) {
if (level === 0) { if (level === 0) {
return pages.map((treeNode, index) => ( return pages.map((treeNode, index) => (
<SidebarGroup key={index}> <Node key={index} node={treeNode} level={level} prefix={prefix} />
<SidebarGroupContent>
<SidebarMenu>
<Node key={index} node={treeNode} level={level} />
</SidebarMenu>
</SidebarGroupContent>
</SidebarGroup>
)); ));
} }
return ( return (
<SidebarMenuSub> <SidebarMenuSub>
{pages.map((treeNode, index) => ( {pages.map((treeNode, index) => (
<Node key={index} node={treeNode} level={level} /> <Node key={index} node={treeNode} level={level} prefix={prefix} />
))} ))}
</SidebarMenuSub> </SidebarMenuSub>
); );
} }
export function DocsNavigation({ pages }: { pages: Cms.ContentItem[] }) { export function DocsNavigation({
pages,
prefix = '/docs',
}: {
pages: Cms.ContentItem[];
prefix?: string;
}) {
return ( return (
<> <>
<Sidebar <Sidebar
variant={'ghost'} variant={'ghost'}
className={'z-1 sticky max-h-full overflow-y-auto'} className={'z-1 sticky max-h-full overflow-y-auto'}
> >
<Tree pages={pages} level={0} /> <SidebarGroup>
<SidebarGroupContent>
<SidebarMenu>
<Tree pages={pages} level={0} prefix={prefix} />
</SidebarMenu>
</SidebarGroupContent>
</SidebarGroup>
</Sidebar> </Sidebar>
<div className={'lg:hidden'}> <div className={'lg:hidden'}>
<FloatingDocumentationNavigation> <FloatingDocumentationNavigation>
<Tree pages={pages} level={0} /> <SidebarGroup>
<SidebarGroupContent>
<SidebarMenu>
<Tree pages={pages} level={0} prefix={prefix} />
</SidebarMenu>
</SidebarGroupContent>
</SidebarGroup>
</FloatingDocumentationNavigation> </FloatingDocumentationNavigation>
</div> </div>
</> </>

View File

@@ -18,7 +18,8 @@ async function docsLoader(language: string | undefined) {
const data = await cms.getContentItems({ const data = await cms.getContentItems({
collection: 'documentation', collection: 'documentation',
language, language,
limit: 500, limit: Infinity,
content: false,
}); });
return data.items; return data.items;

View File

@@ -15,7 +15,7 @@ async function DocsLayout({ children }: React.PropsWithChildren) {
return ( return (
<SidebarProvider <SidebarProvider
style={{ '--sidebar-width': '20em' } as React.CSSProperties} style={{ '--sidebar-width': '20em' } as React.CSSProperties}
className={'lg:container'} className={'h-[calc(100vh-72px)] overflow-y-hidden lg:container'}
> >
<DocsNavigation pages={tree} /> <DocsNavigation pages={tree} />

View File

@@ -2,6 +2,7 @@ import { getSupabaseServerClient } from '@kit/supabase/server-client';
import { SiteFooter } from '~/(marketing)/_components/site-footer'; import { SiteFooter } from '~/(marketing)/_components/site-footer';
import { SiteHeader } from '~/(marketing)/_components/site-header'; import { SiteHeader } from '~/(marketing)/_components/site-header';
import { BackgroundHue } from '~/components/background-hue';
import { withI18n } from '~/lib/i18n/with-i18n'; import { withI18n } from '~/lib/i18n/with-i18n';
async function SiteLayout(props: React.PropsWithChildren) { async function SiteLayout(props: React.PropsWithChildren) {
@@ -17,6 +18,7 @@ async function SiteLayout(props: React.PropsWithChildren) {
{props.children} {props.children}
<BackgroundHue />
<SiteFooter /> <SiteFooter />
</div> </div>
); );

View File

@@ -10,7 +10,6 @@ import {
FeatureGrid, FeatureGrid,
FeatureShowcase, FeatureShowcase,
FeatureShowcaseIconContainer, FeatureShowcaseIconContainer,
GradientSecondaryText,
Hero, Hero,
Pill, Pill,
SecondaryHero, SecondaryHero,
@@ -24,38 +23,40 @@ import { withI18n } from '~/lib/i18n/with-i18n';
function Home() { function Home() {
return ( return (
<div className={'mt-4 flex flex-col space-y-24 py-14'}> <div className={'mt-4 flex flex-col space-y-24 py-14'}>
<Hero <div className={'container mx-auto'}>
pill={ <Hero
<Pill label={'New'}> pill={
<span>The leading SaaS Starter Kit for ambitious developers</span> <Pill label={'New'}>
</Pill> <span>The leading SaaS Starter Kit for ambitious developers</span>
} </Pill>
title={ }
<> title={
<span>The ultimate SaaS Starter</span> <>
<span>for your next project</span> <span>The ultimate SaaS Starter</span>
</> <span>for your next project</span>
} </>
subtitle={ }
<span> subtitle={
Build and Ship a SaaS faster than ever before with the next-gen SaaS <span>
Starter Kit. Ship your SaaS in days, not months. Build and Ship a SaaS faster than ever before with the next-gen
</span> SaaS Starter Kit. Ship your SaaS in days, not months.
} </span>
cta={<MainCallToActionButton />} }
image={ cta={<MainCallToActionButton />}
<Image image={
priority <Image
className={ priority
'rounded-2xl border border-gray-200 dark:border-primary/10' className={
} 'rounded-2xl border border-gray-200 dark:border-primary/10'
width={3558} }
height={2222} width={3558}
src={`/images/dashboard.webp`} height={2222}
alt={`App Image`} src={`/images/dashboard.webp`}
/> alt={`App Image`}
} />
/> }
/>
</div>
<div className={'container mx-auto'}> <div className={'container mx-auto'}>
<div <div
@@ -68,10 +69,10 @@ function Home() {
The ultimate SaaS Starter Kit The ultimate SaaS Starter Kit
</b> </b>
.{' '} .{' '}
<GradientSecondaryText> <span className="font-normal text-muted-foreground">
Unleash your creativity and build your SaaS faster than ever Unleash your creativity and build your SaaS faster than ever
with Makerkit. with Makerkit.
</GradientSecondaryText> </span>
</> </>
} }
icon={ icon={
@@ -83,9 +84,7 @@ function Home() {
> >
<FeatureGrid> <FeatureGrid>
<FeatureCard <FeatureCard
className={ className={'relative col-span-2 overflow-hidden lg:h-96'}
'relative col-span-2 overflow-hidden bg-violet-500 text-white lg:h-96'
}
label={'Beautiful Dashboard'} label={'Beautiful Dashboard'}
description={`Makerkit provides a beautiful dashboard to manage your SaaS business.`} description={`Makerkit provides a beautiful dashboard to manage your SaaS business.`}
> >
@@ -155,7 +154,7 @@ function Home() {
} }
> >
<SecondaryHero <SecondaryHero
pill={<Pill>Get started for free. No credit card required.</Pill>} pill={<Pill label="Start for free">No credit card required.</Pill>}
heading="Fair pricing for all types of businesses" heading="Fair pricing for all types of businesses"
subheading="Get started on our free plan and upgrade when you are ready." subheading="Get started on our free plan and upgrade when you are ready."
/> />

View File

@@ -1,9 +1,16 @@
import { AuthLayoutShell } from '@kit/auth/shared'; import { AuthLayoutShell } from '@kit/auth/shared';
import { AppLogo } from '~/components/app-logo'; import { AppLogo } from '~/components/app-logo';
import { BackgroundHue } from '~/components/background-hue';
function AuthLayout({ children }: React.PropsWithChildren) { function AuthLayout({ children }: React.PropsWithChildren) {
return <AuthLayoutShell Logo={AppLogo}>{children}</AuthLayoutShell>; return (
<AuthLayoutShell Logo={AppLogo}>
{children}
<BackgroundHue />
</AuthLayoutShell>
);
} }
export default AuthLayout; export default AuthLayout;

View File

@@ -54,6 +54,7 @@ async function getContentItems() {
const posts = client const posts = client
.getContentItems({ .getContentItems({
collection: 'posts', collection: 'posts',
content: false,
limit, limit,
}) })
.then((response) => response.items) .then((response) => response.items)
@@ -69,6 +70,7 @@ async function getContentItems() {
const docs = client const docs = client
.getContentItems({ .getContentItems({
collection: 'documentation', collection: 'documentation',
content: false,
limit, limit,
}) })
.then((response) => response.items) .then((response) => response.items)

View File

@@ -31,10 +31,14 @@ export function AppLogo({
label, label,
className, className,
}: { }: {
href?: string; href?: string | null;
className?: string; className?: string;
label?: string; label?: string;
}) { }) {
if (href === null) {
return <LogoImage className={className} />;
}
return ( return (
<Link aria-label={label ?? 'Home Page'} href={href ?? '/'}> <Link aria-label={label ?? 'Home Page'} href={href ?? '/'}>
<LogoImage className={className} /> <LogoImage className={className} />

View File

@@ -0,0 +1,52 @@
const DEFAULT_COLORS_SCALE = {
0: 'hsl(var(--primary))',
1: 'hsl(var(--primary))',
2: 'hsl(var(--primary))',
};
export function BackgroundHue({
className,
opacity = 0.03,
colorsScale = DEFAULT_COLORS_SCALE,
}: {
className?: string;
opacity?: number;
colorsScale?: Record<number, string>;
}) {
const colors = Object.values(colorsScale).map((color, index, array) => {
const offset = array.length > 1 ? index / (array.length - 1) : 0;
const stopOpacity = 1 - index / (array.length - 1);
return (
<stop
offset={offset}
key={index}
style={{ stopColor: color, stopOpacity }}
/>
);
});
return (
<svg
className={`pointer-events-none fixed left-0 top-0 !m-0 hidden h-full w-full dark:block ${className}`}
style={{ opacity }}
xmlns="http://www.w3.org/2000/svg"
viewBox="0 0 100 100"
preserveAspectRatio="none"
>
<defs>
<linearGradient
id="purpleGradient"
x1="10%"
y1="70%"
x2="50%"
y2="20%"
gradientUnits="userSpaceOnUse"
>
{colors}
</linearGradient>
</defs>
<rect width="100" height="100" fill="url(#purpleGradient)" />
</svg>
);
}

View File

@@ -55,8 +55,8 @@
"@makerkit/data-loader-supabase-nextjs": "^1.2.3", "@makerkit/data-loader-supabase-nextjs": "^1.2.3",
"@marsidev/react-turnstile": "^1.1.0", "@marsidev/react-turnstile": "^1.1.0",
"@radix-ui/react-icons": "^1.3.2", "@radix-ui/react-icons": "^1.3.2",
"@supabase/supabase-js": "^2.47.1", "@supabase/supabase-js": "^2.47.2",
"@tanstack/react-query": "5.62.2", "@tanstack/react-query": "5.62.3",
"@tanstack/react-table": "^8.20.5", "@tanstack/react-table": "^8.20.5",
"date-fns": "^4.1.0", "date-fns": "^4.1.0",
"lucide-react": "^0.468.0", "lucide-react": "^0.468.0",
@@ -65,10 +65,10 @@
"next-themes": "0.4.4", "next-themes": "0.4.4",
"react": "19.0.0", "react": "19.0.0",
"react-dom": "19.0.0", "react-dom": "19.0.0",
"react-hook-form": "^7.53.2", "react-hook-form": "^7.54.0",
"react-i18next": "^15.1.3", "react-i18next": "^15.1.3",
"recharts": "2.14.1", "recharts": "2.14.1",
"sonner": "^1.7.0", "sonner": "^1.7.1",
"tailwind-merge": "^2.5.5", "tailwind-merge": "^2.5.5",
"zod": "^3.23.8" "zod": "^3.23.8"
}, },

View File

@@ -27,13 +27,13 @@
"@kit/tailwind-config": "workspace:*", "@kit/tailwind-config": "workspace:*",
"@kit/tsconfig": "workspace:*", "@kit/tsconfig": "workspace:*",
"@kit/ui": "workspace:*", "@kit/ui": "workspace:*",
"@supabase/supabase-js": "^2.47.1", "@supabase/supabase-js": "^2.47.2",
"@types/react": "npm:types-react@19.0.0-rc.1", "@types/react": "npm:types-react@19.0.0-rc.1",
"date-fns": "^4.1.0", "date-fns": "^4.1.0",
"lucide-react": "^0.468.0", "lucide-react": "^0.468.0",
"next": "15.0.4", "next": "15.0.4",
"react": "19.0.0", "react": "19.0.0",
"react-hook-form": "^7.53.2", "react-hook-form": "^7.54.0",
"react-i18next": "^15.1.3", "react-i18next": "^15.1.3",
"zod": "^3.23.8" "zod": "^3.23.8"
}, },

View File

@@ -392,32 +392,36 @@ function PlanIntervalSwitcher(
}>, }>,
) { ) {
return ( return (
<div className={'flex'}> <div className={'flex gap-x-1 rounded-full border p-1.5'}>
{props.intervals.map((plan, index) => { {props.intervals.map((plan, index) => {
const selected = plan === props.interval; const selected = plan === props.interval;
const className = cn( const className = cn(
'animate-in fade-in !outline-none transition-all focus:!ring-0', 'animate-in fade-in rounded-full !outline-none transition-all focus:!ring-0',
{ {
'rounded-r-none border-r-transparent': index === 0, 'border-r-transparent': index === 0,
'rounded-l-none': index === props.intervals.length - 1, ['hover:text-primary text-muted-foreground']: !selected,
['hover:text-primary text-muted-foreground border']: !selected, ['cursor-default font-semibold']: selected,
['hover:text-initial hover:bg-background cursor-default font-semibold']: ['hover:bg-initial']: !selected
selected,
}, },
); );
return ( return (
<Button <Button
key={plan} key={plan}
variant={'outline'} size={'sm'}
variant={selected ? 'default' : 'ghost'}
className={className} className={className}
onClick={() => props.setInterval(plan)} onClick={() => props.setInterval(plan)}
> >
<span className={'flex items-center space-x-1'}> <span className={'flex items-center'}>
<If condition={selected}> <CheckCircle
<CheckCircle className={'animate-in fade-in zoom-in-90 h-4'} /> className={cn('animate-in fade-in zoom-in-95 h-3.5', {
</If> hidden: !selected,
'slide-in-from-left-4': index === 0,
'slide-in-from-right-4': index === props.intervals.length - 1,
})}
/>
<span className={'capitalize'}> <span className={'capitalize'}>
<Trans i18nKey={`common:billingInterval.${plan}`} /> <Trans i18nKey={`common:billingInterval.${plan}`} />

View File

@@ -14,7 +14,7 @@
"./components": "./src/components/index.ts" "./components": "./src/components/index.ts"
}, },
"dependencies": { "dependencies": {
"@lemonsqueezy/lemonsqueezy.js": "3.3.1" "@lemonsqueezy/lemonsqueezy.js": "4.0.0"
}, },
"devDependencies": { "devDependencies": {
"@kit/billing": "workspace:*", "@kit/billing": "workspace:*",

View File

@@ -1,7 +1,7 @@
import { Cms, CmsClient } from '@kit/cms-types'; import { Cms, CmsClient } from '@kit/cms-types';
import { createKeystaticReader } from './create-reader'; import { createKeystaticReader } from './create-reader';
import { PostEntryProps } from './keystatic.config'; import { DocumentationEntryProps, PostEntryProps } from './keystatic.config';
import { renderMarkdoc } from './markdoc'; import { renderMarkdoc } from './markdoc';
export function createKeystaticClient() { export function createKeystaticClient() {
@@ -19,11 +19,13 @@ class KeystaticClient implements CmsClient {
throw new Error(`Collection ${collection} not found`); throw new Error(`Collection ${collection} not found`);
} }
const docs = await reader.collections[collection].all(); const fetchContent = options.content ?? true;
const startOffset = options?.offset ?? 0; const startOffset = options?.offset ?? 0;
const endOffset = startOffset + (options?.limit ?? 10); const endOffset = startOffset + (options?.limit ?? 10);
const docs = await reader.collections[collection].all();
const filtered = docs const filtered = docs
.filter((item) => { .filter((item) => {
const status = options?.status ?? 'published'; const status = options?.status ?? 'published';
@@ -80,72 +82,95 @@ class KeystaticClient implements CmsClient {
return right - left; return right - left;
}); });
function processItems(items: typeof docs) { function processItems(items: typeof filtered) {
const result: typeof docs = [...items]; const slugSet = new Set(items.map((item) => item.slug));
const indexFileCache = new Map<string, boolean>();
const parentCache = new Map<string, string | null>();
const indexFiles = items.filter((item) => { const isIndexFile = (slug: string): boolean => {
const parts = item.slug.split('/'); if (indexFileCache.has(slug)) {
return indexFileCache.get(slug)!;
}
return ( const parts = slug.split('/');
parts.length > 1 &&
parts[parts.length - 1] === parts[parts.length - 2] const result =
); parts.length === 1 ||
}); (parts.length >= 2 &&
parts[parts.length - 1] === parts[parts.length - 2]);
indexFileCache.set(slug, result);
return result;
};
const findClosestValidParent = (pathParts: string[]): string | null => {
const path = pathParts.join('/');
if (parentCache.has(path)) {
return parentCache.get(path)!;
}
function findParentIndex(pathParts: string[]): string | null {
// Try each level up from the current path until we find an index file
for (let i = pathParts.length - 1; i > 0; i--) { for (let i = pathParts.length - 1; i > 0; i--) {
const currentPath = pathParts.slice(0, i).join('/'); const parentParts = pathParts.slice(0, i);
const lastPart = parentParts[parentParts.length - 1];
const possibleParent = indexFiles.find((indexFile) => { if (!lastPart) {
const indexParts = indexFile.slug.split('/'); continue;
const indexFolderPath = indexParts.slice(0, -1).join('/'); }
return indexFolderPath === currentPath; const possibleIndexParent = parentParts.concat(lastPart).join('/');
});
if (possibleParent) { if (slugSet.has(possibleIndexParent)) {
return possibleParent.slug; parentCache.set(path, possibleIndexParent);
return possibleIndexParent;
}
const regularParent = parentParts.join('/');
if (slugSet.has(regularParent)) {
parentCache.set(path, regularParent);
return regularParent;
} }
} }
return null;
}
result.forEach((item) => { parentCache.set(path, null);
// never override the parent if it's already set in the config return null;
if (item.entry.parent) { };
return;
const results = new Array(items.length) as typeof items;
for (let i = 0; i < items.length; i++) {
const item = items[i];
if (!item) {
continue;
}
if (isIndexFile(item.slug)) {
item.entry.parent = null;
results[i] = item;
continue;
} }
const pathParts = item.slug.split('/'); const pathParts = item.slug.split('/');
const parentParts = pathParts.slice(0, -1);
const lastPart = parentParts[parentParts.length - 1]!;
const possibleIndexParent = parentParts.concat(lastPart).join('/');
// Skip if this is a root level index file (e.g., "authentication/authentication") if (slugSet.has(possibleIndexParent)) {
if (pathParts.length === 2 && pathParts[0] === pathParts[1]) { item.entry.parent = possibleIndexParent;
item.entry.parent = null;
return;
}
// Check if current item is an index file
const isIndexFile =
pathParts[pathParts.length - 1] === pathParts[pathParts.length - 2];
if (isIndexFile) {
// For index files, find parent in the level above
const parentPath = pathParts.slice(0, -2);
if (parentPath.length > 0) {
item.entry.parent = findParentIndex(
parentPath.concat(parentPath[parentPath.length - 1]!),
);
} else {
item.entry.parent = null;
}
} else { } else {
// For regular files, find parent in the current folder const regularParent = parentParts.join('/');
item.entry.parent = findParentIndex(pathParts); if (slugSet.has(regularParent)) {
item.entry.parent = regularParent;
} else {
item.entry.parent = findClosestValidParent(pathParts);
}
} }
}); results[i] = item;
}
return result; return results;
} }
const itemsWithParents = processItems(filtered); const itemsWithParents = processItems(filtered);
@@ -156,7 +181,25 @@ class KeystaticClient implements CmsClient {
.sort((a, b) => { .sort((a, b) => {
return (a.entry.order ?? 0) - (b.entry.order ?? 0); return (a.entry.order ?? 0) - (b.entry.order ?? 0);
}) })
.map((item) => this.mapPost(item)), .map((item) => {
if (collection === 'documentation') {
return this.mapDocumentationPost(
item as {
entry: DocumentationEntryProps;
slug: string;
},
{ fetchContent },
);
}
return this.mapPost(
item as {
entry: PostEntryProps;
slug: string;
},
{ fetchContent },
);
}),
); );
return { return {
@@ -192,13 +235,7 @@ class KeystaticClient implements CmsClient {
return Promise.resolve(undefined); return Promise.resolve(undefined);
} }
const allPosts = await reader.collections[collection].all(); return this.mapPost({ entry: doc as PostEntryProps, slug: params.slug });
const children = allPosts.filter(
(item) => item.entry.parent === params.slug,
);
return this.mapPost({ entry: doc, slug: params.slug }, children);
} }
async getCategories() { async getCategories() {
@@ -217,18 +254,79 @@ class KeystaticClient implements CmsClient {
return Promise.resolve(undefined); return Promise.resolve(undefined);
} }
private async mapPost< private async mapDocumentationPost<
Type extends { Type extends {
entry: PostEntryProps; entry: DocumentationEntryProps;
slug: string; slug: string;
}, },
>(item: Type, children: Type[] = []): Promise<Cms.ContentItem> { >(
item: Type,
params: {
fetchContent: boolean;
} = {
fetchContent: true,
},
): Promise<Cms.ContentItem> {
const publishedAt = item.entry.publishedAt const publishedAt = item.entry.publishedAt
? new Date(item.entry.publishedAt) ? new Date(item.entry.publishedAt)
: new Date(); : new Date();
const content = await item.entry.content(); const content = await item.entry.content();
const html = await renderMarkdoc(content.node); const html = params.fetchContent ? await renderMarkdoc(content.node) : [];
return {
id: item.slug,
title: item.entry.title,
label: item.entry.label,
url: item.slug,
slug: item.slug,
description: item.entry.description,
publishedAt: publishedAt.toISOString(),
content: html as string,
image: item.entry.image ?? undefined,
status: item.entry.status,
collapsible: item.entry.collapsible,
collapsed: item.entry.collapsed,
categories:
(item.entry.categories ?? []).map((item) => {
return {
id: item,
name: item,
slug: item,
};
}) ?? [],
tags: (item.entry.tags ?? []).map((item) => {
return {
id: item,
name: item,
slug: item,
};
}),
parentId: item.entry.parent ?? undefined,
order: item.entry.order ?? 1,
children: [],
};
}
private async mapPost<
Type extends {
entry: PostEntryProps;
slug: string;
},
>(
item: Type,
params: {
fetchContent: boolean;
} = {
fetchContent: true,
},
): Promise<Cms.ContentItem> {
const publishedAt = item.entry.publishedAt
? new Date(item.entry.publishedAt)
: new Date();
const content = await item.entry.content();
const html = params.fetchContent ? await renderMarkdoc(content.node) : [];
return { return {
id: item.slug, id: item.slug,
@@ -242,14 +340,14 @@ class KeystaticClient implements CmsClient {
image: item.entry.image ?? undefined, image: item.entry.image ?? undefined,
status: item.entry.status, status: item.entry.status,
categories: categories:
item.entry.categories.map((item) => { (item.entry.categories ?? []).map((item) => {
return { return {
id: item, id: item,
name: item, name: item,
slug: item, slug: item,
}; };
}) ?? [], }) ?? [],
tags: item.entry.tags.map((item) => { tags: (item.entry.tags ?? []).map((item) => {
return { return {
id: item, id: item,
name: item, name: item,
@@ -258,9 +356,7 @@ class KeystaticClient implements CmsClient {
}), }),
parentId: item.entry.parent ?? undefined, parentId: item.entry.parent ?? undefined,
order: item.entry.order ?? 1, order: item.entry.order ?? 1,
children: await Promise.all( children: [],
children.map((child) => this.mapPost(child, [])),
),
}; };
} }
} }

View File

@@ -39,6 +39,10 @@ export type PostEntryProps = Entry<
(typeof keyStaticConfig)['collections']['posts'] (typeof keyStaticConfig)['collections']['posts']
>; >;
export type DocumentationEntryProps = Entry<
(typeof keyStaticConfig)['collections']['documentation']
>;
function createKeyStaticConfig(path = '') { function createKeyStaticConfig(path = '') {
if (path && !path.endsWith('/')) { if (path && !path.endsWith('/')) {
path += '/'; path += '/';
@@ -135,6 +139,14 @@ function getKeystaticCollections(path: string) {
{ label: 'Pending', value: 'pending' }, { label: 'Pending', value: 'pending' },
], ],
}), }),
collapsible: fields.checkbox({
label: 'Collapsible',
defaultValue: false,
}),
collapsed: fields.checkbox({
label: 'Collapsed',
defaultValue: false,
}),
}, },
}), }),
}; };

View File

@@ -16,6 +16,8 @@ export namespace Cms {
order: number; order: number;
children: ContentItem[]; children: ContentItem[];
parentId: string | undefined; parentId: string | undefined;
collapsible?: boolean;
collapsed?: boolean;
} }
export type ContentItemStatus = 'draft' | 'published' | 'review' | 'pending'; export type ContentItemStatus = 'draft' | 'published' | 'review' | 'pending';
@@ -38,6 +40,7 @@ export namespace Cms {
offset?: number; offset?: number;
categories?: string[]; categories?: string[];
tags?: string[]; tags?: string[];
content?: boolean;
parentIds?: string[]; parentIds?: string[];
language?: string | undefined; language?: string | undefined;
sortDirection?: 'asc' | 'desc'; sortDirection?: 'asc' | 'desc';

View File

@@ -23,7 +23,7 @@
"@kit/tailwind-config": "workspace:*", "@kit/tailwind-config": "workspace:*",
"@kit/team-accounts": "workspace:*", "@kit/team-accounts": "workspace:*",
"@kit/tsconfig": "workspace:*", "@kit/tsconfig": "workspace:*",
"@supabase/supabase-js": "^2.47.1", "@supabase/supabase-js": "^2.47.2",
"zod": "^3.23.8" "zod": "^3.23.8"
}, },
"eslintConfig": { "eslintConfig": {

View File

@@ -13,7 +13,7 @@
".": "./src/index.ts" ".": "./src/index.ts"
}, },
"dependencies": { "dependencies": {
"@react-email/components": "0.0.29" "@react-email/components": "0.0.30"
}, },
"devDependencies": { "devDependencies": {
"@kit/eslint-config": "workspace:*", "@kit/eslint-config": "workspace:*",

View File

@@ -34,8 +34,8 @@
"@kit/tsconfig": "workspace:*", "@kit/tsconfig": "workspace:*",
"@kit/ui": "workspace:*", "@kit/ui": "workspace:*",
"@radix-ui/react-icons": "^1.3.2", "@radix-ui/react-icons": "^1.3.2",
"@supabase/supabase-js": "^2.47.1", "@supabase/supabase-js": "^2.47.2",
"@tanstack/react-query": "5.62.2", "@tanstack/react-query": "5.62.3",
"@types/react": "npm:types-react@19.0.0-rc.1", "@types/react": "npm:types-react@19.0.0-rc.1",
"@types/react-dom": "npm:types-react-dom@19.0.0-rc.1", "@types/react-dom": "npm:types-react-dom@19.0.0-rc.1",
"lucide-react": "^0.468.0", "lucide-react": "^0.468.0",
@@ -43,9 +43,9 @@
"next-themes": "0.4.4", "next-themes": "0.4.4",
"react": "19.0.0", "react": "19.0.0",
"react-dom": "19.0.0", "react-dom": "19.0.0",
"react-hook-form": "^7.53.2", "react-hook-form": "^7.54.0",
"react-i18next": "^15.1.3", "react-i18next": "^15.1.3",
"sonner": "^1.7.0", "sonner": "^1.7.1",
"zod": "^3.23.8" "zod": "^3.23.8"
}, },
"prettier": "@kit/prettier-config", "prettier": "@kit/prettier-config",

View File

@@ -148,7 +148,7 @@ export function PersonalAccountDropdown({
<DropdownMenuItem asChild> <DropdownMenuItem asChild>
<Link <Link
className={'s-full flex items-center space-x-2'} className={'s-full flex items-center space-x-2 cursor-pointer'}
href={paths.home} href={paths.home}
> >
<Home className={'h-5'} /> <Home className={'h-5'} />
@@ -162,7 +162,7 @@ export function PersonalAccountDropdown({
<DropdownMenuSeparator /> <DropdownMenuSeparator />
<DropdownMenuItem asChild> <DropdownMenuItem asChild>
<Link className={'s-full flex items-center space-x-2'} href={'/docs'}> <Link className={'s-full flex items-center space-x-2 cursor-pointer'} href={'/docs'}>
<MessageCircleQuestion className={'h-5'} /> <MessageCircleQuestion className={'h-5'} />
<span> <span>
@@ -176,7 +176,7 @@ export function PersonalAccountDropdown({
<DropdownMenuItem asChild> <DropdownMenuItem asChild>
<Link <Link
className={'s-full flex items-center space-x-2'} className={'s-full flex items-center space-x-2 cursor-pointer'}
href={'/admin'} href={'/admin'}
> >
<Shield className={'h-5'} /> <Shield className={'h-5'} />

View File

@@ -21,15 +21,15 @@
"@kit/ui": "workspace:*", "@kit/ui": "workspace:*",
"@makerkit/data-loader-supabase-core": "^0.0.8", "@makerkit/data-loader-supabase-core": "^0.0.8",
"@makerkit/data-loader-supabase-nextjs": "^1.2.3", "@makerkit/data-loader-supabase-nextjs": "^1.2.3",
"@supabase/supabase-js": "^2.47.1", "@supabase/supabase-js": "^2.47.2",
"@tanstack/react-query": "5.62.2", "@tanstack/react-query": "5.62.3",
"@tanstack/react-table": "^8.20.5", "@tanstack/react-table": "^8.20.5",
"@types/react": "npm:types-react@19.0.0-rc.1", "@types/react": "npm:types-react@19.0.0-rc.1",
"lucide-react": "^0.468.0", "lucide-react": "^0.468.0",
"next": "15.0.4", "next": "15.0.4",
"react": "19.0.0", "react": "19.0.0",
"react-dom": "19.0.0", "react-dom": "19.0.0",
"react-hook-form": "^7.53.2", "react-hook-form": "^7.54.0",
"zod": "^3.23.8" "zod": "^3.23.8"
}, },
"exports": { "exports": {

View File

@@ -28,14 +28,14 @@
"@kit/ui": "workspace:*", "@kit/ui": "workspace:*",
"@marsidev/react-turnstile": "^1.1.0", "@marsidev/react-turnstile": "^1.1.0",
"@radix-ui/react-icons": "^1.3.2", "@radix-ui/react-icons": "^1.3.2",
"@supabase/supabase-js": "^2.47.1", "@supabase/supabase-js": "^2.47.2",
"@tanstack/react-query": "5.62.2", "@tanstack/react-query": "5.62.3",
"@types/react": "npm:types-react@19.0.0-rc.1", "@types/react": "npm:types-react@19.0.0-rc.1",
"lucide-react": "^0.468.0", "lucide-react": "^0.468.0",
"next": "15.0.4", "next": "15.0.4",
"react-hook-form": "^7.53.2", "react-hook-form": "^7.54.0",
"react-i18next": "^15.1.3", "react-i18next": "^15.1.3",
"sonner": "^1.7.0", "sonner": "^1.7.1",
"zod": "^3.23.8" "zod": "^3.23.8"
}, },
"prettier": "@kit/prettier-config", "prettier": "@kit/prettier-config",

View File

@@ -20,8 +20,8 @@
"@kit/tailwind-config": "workspace:*", "@kit/tailwind-config": "workspace:*",
"@kit/tsconfig": "workspace:*", "@kit/tsconfig": "workspace:*",
"@kit/ui": "workspace:*", "@kit/ui": "workspace:*",
"@supabase/supabase-js": "^2.47.1", "@supabase/supabase-js": "^2.47.2",
"@tanstack/react-query": "5.62.2", "@tanstack/react-query": "5.62.3",
"@types/react": "npm:types-react@19.0.0-rc.1", "@types/react": "npm:types-react@19.0.0-rc.1",
"lucide-react": "^0.468.0", "lucide-react": "^0.468.0",
"react": "19.0.0", "react": "19.0.0",

View File

@@ -32,8 +32,8 @@
"@kit/tailwind-config": "workspace:*", "@kit/tailwind-config": "workspace:*",
"@kit/tsconfig": "workspace:*", "@kit/tsconfig": "workspace:*",
"@kit/ui": "workspace:*", "@kit/ui": "workspace:*",
"@supabase/supabase-js": "^2.47.1", "@supabase/supabase-js": "^2.47.2",
"@tanstack/react-query": "5.62.2", "@tanstack/react-query": "5.62.3",
"@tanstack/react-table": "^8.20.5", "@tanstack/react-table": "^8.20.5",
"@types/react": "npm:types-react@19.0.0-rc.1", "@types/react": "npm:types-react@19.0.0-rc.1",
"@types/react-dom": "npm:types-react-dom@19.0.0-rc.1", "@types/react-dom": "npm:types-react-dom@19.0.0-rc.1",
@@ -43,9 +43,9 @@
"next": "15.0.4", "next": "15.0.4",
"react": "19.0.0", "react": "19.0.0",
"react-dom": "19.0.0", "react-dom": "19.0.0",
"react-hook-form": "^7.53.2", "react-hook-form": "^7.54.0",
"react-i18next": "^15.1.3", "react-i18next": "^15.1.3",
"sonner": "^1.7.0", "sonner": "^1.7.1",
"zod": "^3.23.8" "zod": "^3.23.8"
}, },
"prettier": "@kit/prettier-config", "prettier": "@kit/prettier-config",

View File

@@ -21,7 +21,7 @@
"@kit/shared": "workspace:*", "@kit/shared": "workspace:*",
"@kit/tailwind-config": "workspace:*", "@kit/tailwind-config": "workspace:*",
"@kit/tsconfig": "workspace:*", "@kit/tsconfig": "workspace:*",
"@tanstack/react-query": "5.62.2", "@tanstack/react-query": "5.62.3",
"next": "15.0.4", "next": "15.0.4",
"react": "19.0.0", "react": "19.0.0",
"react-dom": "19.0.0", "react-dom": "19.0.0",
@@ -29,7 +29,7 @@
}, },
"dependencies": { "dependencies": {
"i18next": "24.0.5", "i18next": "24.0.5",
"i18next-browser-languagedetector": "8.0.0", "i18next-browser-languagedetector": "8.0.1",
"i18next-resources-to-backend": "^1.2.1" "i18next-resources-to-backend": "^1.2.1"
}, },
"eslintConfig": { "eslintConfig": {

View File

@@ -21,7 +21,7 @@
"@kit/supabase": "workspace:*", "@kit/supabase": "workspace:*",
"@kit/tailwind-config": "workspace:*", "@kit/tailwind-config": "workspace:*",
"@kit/tsconfig": "workspace:*", "@kit/tsconfig": "workspace:*",
"@supabase/supabase-js": "^2.47.1", "@supabase/supabase-js": "^2.47.2",
"next": "15.0.4", "next": "15.0.4",
"zod": "^3.23.8" "zod": "^3.23.8"
}, },

View File

@@ -29,8 +29,8 @@
"@kit/tailwind-config": "workspace:*", "@kit/tailwind-config": "workspace:*",
"@kit/tsconfig": "workspace:*", "@kit/tsconfig": "workspace:*",
"@supabase/ssr": "^0.5.2", "@supabase/ssr": "^0.5.2",
"@supabase/supabase-js": "^2.47.1", "@supabase/supabase-js": "^2.47.2",
"@tanstack/react-query": "5.62.2", "@tanstack/react-query": "5.62.3",
"@types/react": "npm:types-react@19.0.0-rc.1", "@types/react": "npm:types-react@19.0.0-rc.1",
"next": "15.0.4", "next": "15.0.4",
"react": "19.0.0", "react": "19.0.0",

View File

@@ -43,7 +43,7 @@
"@kit/tailwind-config": "workspace:*", "@kit/tailwind-config": "workspace:*",
"@kit/tsconfig": "workspace:*", "@kit/tsconfig": "workspace:*",
"@radix-ui/react-icons": "^1.3.2", "@radix-ui/react-icons": "^1.3.2",
"@tanstack/react-query": "5.62.2", "@tanstack/react-query": "5.62.3",
"@tanstack/react-table": "^8.20.5", "@tanstack/react-table": "^8.20.5",
"@types/react": "npm:types-react@19.0.0-rc.1", "@types/react": "npm:types-react@19.0.0-rc.1",
"@types/react-dom": "npm:types-react-dom@19.0.0-rc.1", "@types/react-dom": "npm:types-react-dom@19.0.0-rc.1",
@@ -54,9 +54,9 @@
"next-themes": "0.4.4", "next-themes": "0.4.4",
"prettier": "^3.4.2", "prettier": "^3.4.2",
"react-day-picker": "^8.10.1", "react-day-picker": "^8.10.1",
"react-hook-form": "^7.53.2", "react-hook-form": "^7.54.0",
"react-i18next": "^15.1.3", "react-i18next": "^15.1.3",
"sonner": "^1.7.0", "sonner": "^1.7.1",
"tailwindcss": "3.4.16", "tailwindcss": "3.4.16",
"tailwindcss-animate": "^1.0.7", "tailwindcss-animate": "^1.0.7",
"typescript": "^5.7.2", "typescript": "^5.7.2",
@@ -106,6 +106,7 @@
"./chart": "./src/shadcn/chart.tsx", "./chart": "./src/shadcn/chart.tsx",
"./skeleton": "./src/shadcn/skeleton.tsx", "./skeleton": "./src/shadcn/skeleton.tsx",
"./shadcn-sidebar": "./src/shadcn/sidebar.tsx", "./shadcn-sidebar": "./src/shadcn/sidebar.tsx",
"./collapsible": "./src/shadcn/collapsible.tsx",
"./utils": "./src/lib/utils/index.ts", "./utils": "./src/lib/utils/index.ts",
"./if": "./src/makerkit/if.tsx", "./if": "./src/makerkit/if.tsx",
"./trans": "./src/makerkit/trans.tsx", "./trans": "./src/makerkit/trans.tsx",

View File

@@ -30,7 +30,7 @@ export const FeatureCard = forwardRef<HTMLDivElement, FeatureCardProps>(
> >
<CardHeader> <CardHeader>
<CardTitle className="text-xl font-semibold">{label}</CardTitle> <CardTitle className="text-xl font-semibold">{label}</CardTitle>
<CardDescription className="max-w-xs text-sm font-semibold tracking-tight text-current"> <CardDescription className="max-w-xs text-sm font-semibold tracking-tight text-muted-foreground">
{description} {description}
</CardDescription> </CardDescription>
</CardHeader> </CardHeader>

View File

@@ -10,7 +10,7 @@ export const FeatureGrid = forwardRef<
<div <div
ref={ref} ref={ref}
className={cn( className={cn(
'grid w-full grid-cols-1 gap-6 space-y-0 lg:grid-cols-3', 'grid w-full grid-cols-1 gap-4 space-y-0 lg:grid-cols-3',
className, className,
)} )}
{...props} {...props}

View File

@@ -20,7 +20,7 @@ export const FeatureShowcase = forwardRef<HTMLDivElement, FeatureShowcaseProps>(
> >
<div className="flex w-full max-w-5xl flex-col space-y-4"> <div className="flex w-full max-w-5xl flex-col space-y-4">
{icon && <div className="flex">{icon}</div>} {icon && <div className="flex">{icon}</div>}
<h3 className="text-3xl font-normal tracking-tighter xl:text-5xl"> <h3 className="text-3xl font-normal tracking-tight xl:text-5xl">
{heading} {heading}
</h3> </h3>
</div> </div>

View File

@@ -24,7 +24,7 @@ export const Header = forwardRef<HTMLDivElement, HeaderProps>(
> >
<div className="container"> <div className="container">
<div className="grid h-14 grid-cols-3 items-center"> <div className="grid h-14 grid-cols-3 items-center">
<div>{logo}</div> <div className={'mx-auto lg:mx-0'}>{logo}</div>
<div className="order-first md:order-none">{navigation}</div> <div className="order-first md:order-none">{navigation}</div>
<div className="flex items-center justify-end space-x-1"> <div className="flex items-center justify-end space-x-1">
{actions} {actions}

View File

@@ -16,7 +16,7 @@ export const HeroTitle = forwardRef<
<Comp <Comp
ref={ref} ref={ref}
className={cn( className={cn(
'hero-title flex flex-col space-y-1 text-center font-sans text-4xl font-semibold tracking-tighter dark:text-white sm:text-6xl lg:max-w-5xl lg:text-7xl xl:text-[5.125rem]', 'hero-title flex flex-col text-center font-sans text-4xl font-semibold tracking-tighter dark:text-white sm:text-6xl lg:max-w-5xl lg:text-7xl xl:text-[4.85rem]',
className, className,
)} )}
{...props} {...props}

View File

@@ -1,7 +1,6 @@
import React from 'react'; import React from 'react';
import { cn } from '../../lib/utils'; import { cn } from '../../lib/utils';
import { Heading } from '../../shadcn/heading';
import { HeroTitle } from './hero-title'; import { HeroTitle } from './hero-title';
interface HeroProps { interface HeroProps {
@@ -53,12 +52,11 @@ export function Hero({
{subtitle && ( {subtitle && (
<div className="flex max-w-2xl flex-col space-y-1"> <div className="flex max-w-2xl flex-col space-y-1">
<Heading <h3
level={3} className="p-0 text-center text-xl font-sans font-normal text-muted-foreground tracking-tight"
className="p-0 text-center font-sans text-base font-normal"
> >
{subtitle} {subtitle}
</Heading> </h3>
</div> </div>
)} )}
</div> </div>
@@ -78,8 +76,11 @@ export function Hero({
{image && ( {image && (
<div <div
className={cn('mx-auto flex max-w-[85rem] justify-center py-8', { style={{
['delay-300 duration-1000 animate-in fade-in zoom-in-95 slide-in-from-top-32 fill-mode-both']: MozAnimationDuration: '100ms',
}}
className={cn('mx-auto container flex justify-center py-8', {
['delay-1000 duration-1000 animate-in fade-in zoom-in-95 slide-in-from-top-32 fill-mode-both']:
animate, animate,
})} })}
> >

View File

@@ -3,7 +3,6 @@ import { forwardRef } from 'react';
import { Slot, Slottable } from '@radix-ui/react-slot'; import { Slot, Slottable } from '@radix-ui/react-slot';
import { cn } from '../../lib/utils'; import { cn } from '../../lib/utils';
import { GradientSecondaryText } from './gradient-secondary-text';
export const Pill = forwardRef< export const Pill = forwardRef<
HTMLHeadingElement, HTMLHeadingElement,
@@ -33,7 +32,7 @@ export const Pill = forwardRef<
</span> </span>
)} )}
<Slottable> <Slottable>
<GradientSecondaryText>{props.children}</GradientSecondaryText> <span className={'text-secondary-foreground'}>{props.children}</span>
</Slottable> </Slottable>
</Comp> </Comp>
); );

View File

@@ -18,7 +18,7 @@ export const SecondaryHero = forwardRef<HTMLDivElement, SecondaryHeroProps>(
<div <div
ref={ref} ref={ref}
className={cn( className={cn(
'flex flex-col items-center space-y-4 text-center', 'flex flex-col items-center space-y-6 text-center',
className, className,
)} )}
{...props} {...props}
@@ -30,12 +30,11 @@ export const SecondaryHero = forwardRef<HTMLDivElement, SecondaryHeroProps>(
{heading} {heading}
</Heading> </Heading>
<Heading <h3
level={3} className="font-sans font-normal text-xl tracking-tight text-muted-foreground"
className="font-sans font-normal tracking-tight text-muted-foreground"
> >
{subheading} {subheading}
</Heading> </h3>
</div> </div>
{children} {children}

View File

@@ -74,7 +74,7 @@ export function SubMenuModeToggle() {
return ( return (
<DropdownMenuItem <DropdownMenuItem
className={cn('flex items-center space-x-2', { className={cn('flex items-center space-x-2 cursor-pointer', {
'bg-muted': isSelected, 'bg-muted': isSelected,
})} })}
key={mode} key={mode}

458
pnpm-lock.yaml generated

File diff suppressed because it is too large Load Diff

View File

@@ -9,7 +9,7 @@
"typecheck": "tsc --noEmit" "typecheck": "tsc --noEmit"
}, },
"dependencies": { "dependencies": {
"@trivago/prettier-plugin-sort-imports": "^4.3.0", "@trivago/prettier-plugin-sort-imports": "^5.1.0",
"prettier": "^3.4.2", "prettier": "^3.4.2",
"prettier-plugin-tailwindcss": "^0.6.9" "prettier-plugin-tailwindcss": "^0.6.9"
}, },