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 {
@apply mt-14 text-4xl font-semibold font-heading tracking-tight ;
@apply mt-14 text-4xl font-semibold font-heading tracking-tight;
}
.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 {
@apply mt-10 text-xl font-semibold font-heading tracking-tight;
@apply mt-12 text-xl font-semibold font-heading tracking-tight;
}
.HTML h4 {
@@ -41,11 +41,11 @@ For more info: https://github.com/tailwindlabs/tailwindcss/issues/3258#issuecomm
}
.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 {
@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 {
@@ -55,7 +55,7 @@ For more info: https://github.com/tailwindlabs/tailwindcss/issues/3258#issuecomm
}
.HTML ol > li:before {
@apply inline-flex font-medium;
@apply inline-flex font-medium text-muted-foreground;
content: counters(counts, '.') '. ';
font-feature-settings: 'tnum';
@@ -63,7 +63,7 @@ For more info: https://github.com/tailwindlabs/tailwindcss/issues/3258#issuecomm
.HTML b,
.HTML strong {
@apply font-bold;
@apply font-semibold text-secondary-foreground;
}
:global(.dark) .HTML b,
@@ -92,10 +92,35 @@ For more info: https://github.com/tailwindlabs/tailwindcss/issues/3258#issuecomm
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 {
@apply my-4 border-l-8 border border-primary px-6 py-4 text-lg font-medium text-muted-foreground;
}
.HTML pre {
@apply my-6 text-sm text-current border p-6 rounded-lg overflow-x-scroll;
.HTML a {
@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,
offset,
language,
content: false,
sortBy: 'publishedAt',
sortDirection: 'desc',
});

View File

@@ -61,13 +61,15 @@ async function DocumentationPage({ params }: DocumentationPageProps) {
);
return (
<div className={'flex flex-1 flex-col space-y-4'}>
<div className={'flex'}>
<article className={cn(styles.HTML, 'container space-y-12')}>
<div className={'flex flex-1 flex-col space-y-4 overflow-y-hidden'}>
<div className={'flex overflow-y-hidden'}>
<article
className={cn(styles.HTML, 'container space-y-12 overflow-y-auto')}
>
<section className={'flex flex-col space-y-4 pt-6'}>
<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}
</h2>
</section>

View File

@@ -1,13 +1,20 @@
'use client';
import { useRef } from 'react';
import Link from 'next/link';
import { usePathname } from 'next/navigation';
import { SidebarMenuButton, SidebarMenuItem } from '@kit/ui/shadcn-sidebar';
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 ref = useRef<HTMLElement>(null);
const isCurrent = isRouteActive(url, currentPath, true);
return (
@@ -15,10 +22,16 @@ export function DocsNavLink({ label, url }: { label: string; url: string }) {
<SidebarMenuButton
asChild
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}>
<span className="block max-w-full truncate">{label}</span>
<span ref={ref} className="block max-w-full truncate">
{label}
</span>
{children}
</Link>
</SidebarMenuButton>
</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 { CollapsibleContent, CollapsibleTrigger } from '@kit/ui/collapsible';
import {
Sidebar,
SidebarGroup,
SidebarGroupContent,
SidebarMenu,
SidebarMenuButton,
SidebarMenuItem,
SidebarMenuSub,
} from '@kit/ui/shadcn-sidebar';
import { DocsNavLink } from '~/(marketing)/docs/_components/docs-nav-link';
import { DocsNavigationCollapsible } from '~/(marketing)/docs/_components/docs-navigation-collapsible';
import { FloatingDocumentationNavigation } from './floating-docs-navigation';
function Node({ node, level }: { node: Cms.ContentItem; level: number }) {
const pathPrefix = `/docs`;
const url = `${pathPrefix}/${node.slug}`;
function Node({
node,
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 (
<>
<DocsNavLink label={node.title} url={url} />
<Container>
<Trigger />
{(node.children ?? []).length > 0 && (
<Tree pages={node.children ?? []} level={level + 1} />
)}
</>
<ContentContainer>
<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) {
return pages.map((treeNode, index) => (
<SidebarGroup key={index}>
<SidebarGroupContent>
<SidebarMenu>
<Node key={index} node={treeNode} level={level} />
</SidebarMenu>
</SidebarGroupContent>
</SidebarGroup>
<Node key={index} node={treeNode} level={level} prefix={prefix} />
));
}
return (
<SidebarMenuSub>
{pages.map((treeNode, index) => (
<Node key={index} node={treeNode} level={level} />
<Node key={index} node={treeNode} level={level} prefix={prefix} />
))}
</SidebarMenuSub>
);
}
export function DocsNavigation({ pages }: { pages: Cms.ContentItem[] }) {
export function DocsNavigation({
pages,
prefix = '/docs',
}: {
pages: Cms.ContentItem[];
prefix?: string;
}) {
return (
<>
<Sidebar
variant={'ghost'}
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>
<div className={'lg:hidden'}>
<FloatingDocumentationNavigation>
<Tree pages={pages} level={0} />
<SidebarGroup>
<SidebarGroupContent>
<SidebarMenu>
<Tree pages={pages} level={0} prefix={prefix} />
</SidebarMenu>
</SidebarGroupContent>
</SidebarGroup>
</FloatingDocumentationNavigation>
</div>
</>

View File

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

View File

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

View File

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

View File

@@ -10,7 +10,6 @@ import {
FeatureGrid,
FeatureShowcase,
FeatureShowcaseIconContainer,
GradientSecondaryText,
Hero,
Pill,
SecondaryHero,
@@ -24,38 +23,40 @@ import { withI18n } from '~/lib/i18n/with-i18n';
function Home() {
return (
<div className={'mt-4 flex flex-col space-y-24 py-14'}>
<Hero
pill={
<Pill label={'New'}>
<span>The leading SaaS Starter Kit for ambitious developers</span>
</Pill>
}
title={
<>
<span>The ultimate SaaS Starter</span>
<span>for your next project</span>
</>
}
subtitle={
<span>
Build and Ship a SaaS faster than ever before with the next-gen SaaS
Starter Kit. Ship your SaaS in days, not months.
</span>
}
cta={<MainCallToActionButton />}
image={
<Image
priority
className={
'rounded-2xl border border-gray-200 dark:border-primary/10'
}
width={3558}
height={2222}
src={`/images/dashboard.webp`}
alt={`App Image`}
/>
}
/>
<div className={'container mx-auto'}>
<Hero
pill={
<Pill label={'New'}>
<span>The leading SaaS Starter Kit for ambitious developers</span>
</Pill>
}
title={
<>
<span>The ultimate SaaS Starter</span>
<span>for your next project</span>
</>
}
subtitle={
<span>
Build and Ship a SaaS faster than ever before with the next-gen
SaaS Starter Kit. Ship your SaaS in days, not months.
</span>
}
cta={<MainCallToActionButton />}
image={
<Image
priority
className={
'rounded-2xl border border-gray-200 dark:border-primary/10'
}
width={3558}
height={2222}
src={`/images/dashboard.webp`}
alt={`App Image`}
/>
}
/>
</div>
<div className={'container mx-auto'}>
<div
@@ -68,10 +69,10 @@ function Home() {
The ultimate SaaS Starter Kit
</b>
.{' '}
<GradientSecondaryText>
<span className="font-normal text-muted-foreground">
Unleash your creativity and build your SaaS faster than ever
with Makerkit.
</GradientSecondaryText>
</span>
</>
}
icon={
@@ -83,9 +84,7 @@ function Home() {
>
<FeatureGrid>
<FeatureCard
className={
'relative col-span-2 overflow-hidden bg-violet-500 text-white lg:h-96'
}
className={'relative col-span-2 overflow-hidden lg:h-96'}
label={'Beautiful Dashboard'}
description={`Makerkit provides a beautiful dashboard to manage your SaaS business.`}
>
@@ -155,7 +154,7 @@ function Home() {
}
>
<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"
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 { AppLogo } from '~/components/app-logo';
import { BackgroundHue } from '~/components/background-hue';
function AuthLayout({ children }: React.PropsWithChildren) {
return <AuthLayoutShell Logo={AppLogo}>{children}</AuthLayoutShell>;
return (
<AuthLayoutShell Logo={AppLogo}>
{children}
<BackgroundHue />
</AuthLayoutShell>
);
}
export default AuthLayout;

View File

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

View File

@@ -31,10 +31,14 @@ export function AppLogo({
label,
className,
}: {
href?: string;
href?: string | null;
className?: string;
label?: string;
}) {
if (href === null) {
return <LogoImage className={className} />;
}
return (
<Link aria-label={label ?? 'Home Page'} href={href ?? '/'}>
<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",
"@marsidev/react-turnstile": "^1.1.0",
"@radix-ui/react-icons": "^1.3.2",
"@supabase/supabase-js": "^2.47.1",
"@tanstack/react-query": "5.62.2",
"@supabase/supabase-js": "^2.47.2",
"@tanstack/react-query": "5.62.3",
"@tanstack/react-table": "^8.20.5",
"date-fns": "^4.1.0",
"lucide-react": "^0.468.0",
@@ -65,10 +65,10 @@
"next-themes": "0.4.4",
"react": "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",
"recharts": "2.14.1",
"sonner": "^1.7.0",
"sonner": "^1.7.1",
"tailwind-merge": "^2.5.5",
"zod": "^3.23.8"
},