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:
committed by
GitHub
parent
a682b991f3
commit
079a8f857a
@@ -7,11 +7,11 @@
|
||||
}
|
||||
|
||||
.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;
|
||||
}
|
||||
@@ -37,6 +37,7 @@ const getContentItems = cache(
|
||||
limit,
|
||||
offset,
|
||||
language,
|
||||
content: false,
|
||||
sortBy: 'publishedAt',
|
||||
sortDirection: 'desc',
|
||||
});
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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>
|
||||
);
|
||||
}
|
||||
@@ -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 (
|
||||
<>
|
||||
<DocsNavLink label={node.title} url={url} />
|
||||
|
||||
{(node.children ?? []).length > 0 && (
|
||||
<Tree pages={node.children ?? []} level={level + 1} />
|
||||
)}
|
||||
</>
|
||||
<DocsNavigationCollapsible node={node} prefix={prefix}>
|
||||
{props.children}
|
||||
</DocsNavigationCollapsible>
|
||||
);
|
||||
}
|
||||
|
||||
function Tree({ pages, level }: { pages: Cms.ContentItem[]; level: number }) {
|
||||
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 (
|
||||
<Container>
|
||||
<Trigger />
|
||||
|
||||
<ContentContainer>
|
||||
<Tree pages={node.children ?? []} level={level + 1} prefix={prefix} />
|
||||
</ContentContainer>
|
||||
</Container>
|
||||
);
|
||||
}
|
||||
|
||||
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>
|
||||
</>
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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} />
|
||||
|
||||
|
||||
@@ -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>
|
||||
);
|
||||
|
||||
@@ -10,7 +10,6 @@ import {
|
||||
FeatureGrid,
|
||||
FeatureShowcase,
|
||||
FeatureShowcaseIconContainer,
|
||||
GradientSecondaryText,
|
||||
Hero,
|
||||
Pill,
|
||||
SecondaryHero,
|
||||
@@ -24,6 +23,7 @@ import { withI18n } from '~/lib/i18n/with-i18n';
|
||||
function Home() {
|
||||
return (
|
||||
<div className={'mt-4 flex flex-col space-y-24 py-14'}>
|
||||
<div className={'container mx-auto'}>
|
||||
<Hero
|
||||
pill={
|
||||
<Pill label={'New'}>
|
||||
@@ -38,8 +38,8 @@ function Home() {
|
||||
}
|
||||
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.
|
||||
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 />}
|
||||
@@ -56,6 +56,7 @@ function Home() {
|
||||
/>
|
||||
}
|
||||
/>
|
||||
</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."
|
||||
/>
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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} />
|
||||
|
||||
52
apps/web/components/background-hue.tsx
Normal file
52
apps/web/components/background-hue.tsx
Normal 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>
|
||||
);
|
||||
}
|
||||
@@ -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"
|
||||
},
|
||||
|
||||
@@ -27,13 +27,13 @@
|
||||
"@kit/tailwind-config": "workspace:*",
|
||||
"@kit/tsconfig": "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",
|
||||
"date-fns": "^4.1.0",
|
||||
"lucide-react": "^0.468.0",
|
||||
"next": "15.0.4",
|
||||
"react": "19.0.0",
|
||||
"react-hook-form": "^7.53.2",
|
||||
"react-hook-form": "^7.54.0",
|
||||
"react-i18next": "^15.1.3",
|
||||
"zod": "^3.23.8"
|
||||
},
|
||||
|
||||
@@ -392,32 +392,36 @@ function PlanIntervalSwitcher(
|
||||
}>,
|
||||
) {
|
||||
return (
|
||||
<div className={'flex'}>
|
||||
<div className={'flex gap-x-1 rounded-full border p-1.5'}>
|
||||
{props.intervals.map((plan, index) => {
|
||||
const selected = plan === props.interval;
|
||||
|
||||
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,
|
||||
'rounded-l-none': index === props.intervals.length - 1,
|
||||
['hover:text-primary text-muted-foreground border']: !selected,
|
||||
['hover:text-initial hover:bg-background cursor-default font-semibold']:
|
||||
selected,
|
||||
'border-r-transparent': index === 0,
|
||||
['hover:text-primary text-muted-foreground']: !selected,
|
||||
['cursor-default font-semibold']: selected,
|
||||
['hover:bg-initial']: !selected
|
||||
},
|
||||
);
|
||||
|
||||
return (
|
||||
<Button
|
||||
key={plan}
|
||||
variant={'outline'}
|
||||
size={'sm'}
|
||||
variant={selected ? 'default' : 'ghost'}
|
||||
className={className}
|
||||
onClick={() => props.setInterval(plan)}
|
||||
>
|
||||
<span className={'flex items-center space-x-1'}>
|
||||
<If condition={selected}>
|
||||
<CheckCircle className={'animate-in fade-in zoom-in-90 h-4'} />
|
||||
</If>
|
||||
<span className={'flex items-center'}>
|
||||
<CheckCircle
|
||||
className={cn('animate-in fade-in zoom-in-95 h-3.5', {
|
||||
hidden: !selected,
|
||||
'slide-in-from-left-4': index === 0,
|
||||
'slide-in-from-right-4': index === props.intervals.length - 1,
|
||||
})}
|
||||
/>
|
||||
|
||||
<span className={'capitalize'}>
|
||||
<Trans i18nKey={`common:billingInterval.${plan}`} />
|
||||
|
||||
@@ -14,7 +14,7 @@
|
||||
"./components": "./src/components/index.ts"
|
||||
},
|
||||
"dependencies": {
|
||||
"@lemonsqueezy/lemonsqueezy.js": "3.3.1"
|
||||
"@lemonsqueezy/lemonsqueezy.js": "4.0.0"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@kit/billing": "workspace:*",
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import { Cms, CmsClient } from '@kit/cms-types';
|
||||
|
||||
import { createKeystaticReader } from './create-reader';
|
||||
import { PostEntryProps } from './keystatic.config';
|
||||
import { DocumentationEntryProps, PostEntryProps } from './keystatic.config';
|
||||
import { renderMarkdoc } from './markdoc';
|
||||
|
||||
export function createKeystaticClient() {
|
||||
@@ -19,11 +19,13 @@ class KeystaticClient implements CmsClient {
|
||||
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 endOffset = startOffset + (options?.limit ?? 10);
|
||||
|
||||
const docs = await reader.collections[collection].all();
|
||||
|
||||
const filtered = docs
|
||||
.filter((item) => {
|
||||
const status = options?.status ?? 'published';
|
||||
@@ -80,72 +82,95 @@ class KeystaticClient implements CmsClient {
|
||||
return right - left;
|
||||
});
|
||||
|
||||
function processItems(items: typeof docs) {
|
||||
const result: typeof docs = [...items];
|
||||
function processItems(items: typeof filtered) {
|
||||
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 parts = item.slug.split('/');
|
||||
const isIndexFile = (slug: string): boolean => {
|
||||
if (indexFileCache.has(slug)) {
|
||||
return indexFileCache.get(slug)!;
|
||||
}
|
||||
|
||||
return (
|
||||
parts.length > 1 &&
|
||||
parts[parts.length - 1] === parts[parts.length - 2]
|
||||
);
|
||||
});
|
||||
const parts = slug.split('/');
|
||||
|
||||
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--) {
|
||||
const currentPath = pathParts.slice(0, i).join('/');
|
||||
const parentParts = pathParts.slice(0, i);
|
||||
const lastPart = parentParts[parentParts.length - 1];
|
||||
|
||||
const possibleParent = indexFiles.find((indexFile) => {
|
||||
const indexParts = indexFile.slug.split('/');
|
||||
const indexFolderPath = indexParts.slice(0, -1).join('/');
|
||||
if (!lastPart) {
|
||||
continue;
|
||||
}
|
||||
|
||||
return indexFolderPath === currentPath;
|
||||
});
|
||||
const possibleIndexParent = parentParts.concat(lastPart).join('/');
|
||||
|
||||
if (possibleParent) {
|
||||
return possibleParent.slug;
|
||||
if (slugSet.has(possibleIndexParent)) {
|
||||
parentCache.set(path, possibleIndexParent);
|
||||
return possibleIndexParent;
|
||||
}
|
||||
|
||||
const regularParent = parentParts.join('/');
|
||||
|
||||
if (slugSet.has(regularParent)) {
|
||||
parentCache.set(path, regularParent);
|
||||
return regularParent;
|
||||
}
|
||||
}
|
||||
|
||||
parentCache.set(path, null);
|
||||
return null;
|
||||
};
|
||||
|
||||
const results = new Array(items.length) as typeof items;
|
||||
|
||||
for (let i = 0; i < items.length; i++) {
|
||||
const item = items[i];
|
||||
|
||||
if (!item) {
|
||||
continue;
|
||||
}
|
||||
|
||||
result.forEach((item) => {
|
||||
// never override the parent if it's already set in the config
|
||||
if (item.entry.parent) {
|
||||
return;
|
||||
if (isIndexFile(item.slug)) {
|
||||
item.entry.parent = null;
|
||||
results[i] = item;
|
||||
continue;
|
||||
}
|
||||
|
||||
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 (pathParts.length === 2 && pathParts[0] === pathParts[1]) {
|
||||
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]!),
|
||||
);
|
||||
if (slugSet.has(possibleIndexParent)) {
|
||||
item.entry.parent = possibleIndexParent;
|
||||
} else {
|
||||
item.entry.parent = null;
|
||||
}
|
||||
const regularParent = parentParts.join('/');
|
||||
if (slugSet.has(regularParent)) {
|
||||
item.entry.parent = regularParent;
|
||||
} else {
|
||||
// For regular files, find parent in the current folder
|
||||
item.entry.parent = findParentIndex(pathParts);
|
||||
item.entry.parent = findClosestValidParent(pathParts);
|
||||
}
|
||||
}
|
||||
results[i] = item;
|
||||
}
|
||||
});
|
||||
|
||||
return result;
|
||||
return results;
|
||||
}
|
||||
|
||||
const itemsWithParents = processItems(filtered);
|
||||
@@ -156,7 +181,25 @@ class KeystaticClient implements CmsClient {
|
||||
.sort((a, b) => {
|
||||
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 {
|
||||
@@ -192,13 +235,7 @@ class KeystaticClient implements CmsClient {
|
||||
return Promise.resolve(undefined);
|
||||
}
|
||||
|
||||
const allPosts = await reader.collections[collection].all();
|
||||
|
||||
const children = allPosts.filter(
|
||||
(item) => item.entry.parent === params.slug,
|
||||
);
|
||||
|
||||
return this.mapPost({ entry: doc, slug: params.slug }, children);
|
||||
return this.mapPost({ entry: doc as PostEntryProps, slug: params.slug });
|
||||
}
|
||||
|
||||
async getCategories() {
|
||||
@@ -217,18 +254,79 @@ class KeystaticClient implements CmsClient {
|
||||
return Promise.resolve(undefined);
|
||||
}
|
||||
|
||||
private async mapPost<
|
||||
private async mapDocumentationPost<
|
||||
Type extends {
|
||||
entry: PostEntryProps;
|
||||
entry: DocumentationEntryProps;
|
||||
slug: string;
|
||||
},
|
||||
>(item: Type, children: Type[] = []): Promise<Cms.ContentItem> {
|
||||
>(
|
||||
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 = 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 {
|
||||
id: item.slug,
|
||||
@@ -242,14 +340,14 @@ class KeystaticClient implements CmsClient {
|
||||
image: item.entry.image ?? undefined,
|
||||
status: item.entry.status,
|
||||
categories:
|
||||
item.entry.categories.map((item) => {
|
||||
(item.entry.categories ?? []).map((item) => {
|
||||
return {
|
||||
id: item,
|
||||
name: item,
|
||||
slug: item,
|
||||
};
|
||||
}) ?? [],
|
||||
tags: item.entry.tags.map((item) => {
|
||||
tags: (item.entry.tags ?? []).map((item) => {
|
||||
return {
|
||||
id: item,
|
||||
name: item,
|
||||
@@ -258,9 +356,7 @@ class KeystaticClient implements CmsClient {
|
||||
}),
|
||||
parentId: item.entry.parent ?? undefined,
|
||||
order: item.entry.order ?? 1,
|
||||
children: await Promise.all(
|
||||
children.map((child) => this.mapPost(child, [])),
|
||||
),
|
||||
children: [],
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
@@ -39,6 +39,10 @@ export type PostEntryProps = Entry<
|
||||
(typeof keyStaticConfig)['collections']['posts']
|
||||
>;
|
||||
|
||||
export type DocumentationEntryProps = Entry<
|
||||
(typeof keyStaticConfig)['collections']['documentation']
|
||||
>;
|
||||
|
||||
function createKeyStaticConfig(path = '') {
|
||||
if (path && !path.endsWith('/')) {
|
||||
path += '/';
|
||||
@@ -135,6 +139,14 @@ function getKeystaticCollections(path: string) {
|
||||
{ label: 'Pending', value: 'pending' },
|
||||
],
|
||||
}),
|
||||
collapsible: fields.checkbox({
|
||||
label: 'Collapsible',
|
||||
defaultValue: false,
|
||||
}),
|
||||
collapsed: fields.checkbox({
|
||||
label: 'Collapsed',
|
||||
defaultValue: false,
|
||||
}),
|
||||
},
|
||||
}),
|
||||
};
|
||||
|
||||
@@ -16,6 +16,8 @@ export namespace Cms {
|
||||
order: number;
|
||||
children: ContentItem[];
|
||||
parentId: string | undefined;
|
||||
collapsible?: boolean;
|
||||
collapsed?: boolean;
|
||||
}
|
||||
|
||||
export type ContentItemStatus = 'draft' | 'published' | 'review' | 'pending';
|
||||
@@ -38,6 +40,7 @@ export namespace Cms {
|
||||
offset?: number;
|
||||
categories?: string[];
|
||||
tags?: string[];
|
||||
content?: boolean;
|
||||
parentIds?: string[];
|
||||
language?: string | undefined;
|
||||
sortDirection?: 'asc' | 'desc';
|
||||
|
||||
@@ -23,7 +23,7 @@
|
||||
"@kit/tailwind-config": "workspace:*",
|
||||
"@kit/team-accounts": "workspace:*",
|
||||
"@kit/tsconfig": "workspace:*",
|
||||
"@supabase/supabase-js": "^2.47.1",
|
||||
"@supabase/supabase-js": "^2.47.2",
|
||||
"zod": "^3.23.8"
|
||||
},
|
||||
"eslintConfig": {
|
||||
|
||||
@@ -13,7 +13,7 @@
|
||||
".": "./src/index.ts"
|
||||
},
|
||||
"dependencies": {
|
||||
"@react-email/components": "0.0.29"
|
||||
"@react-email/components": "0.0.30"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@kit/eslint-config": "workspace:*",
|
||||
|
||||
@@ -34,8 +34,8 @@
|
||||
"@kit/tsconfig": "workspace:*",
|
||||
"@kit/ui": "workspace:*",
|
||||
"@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",
|
||||
"@types/react": "npm:types-react@19.0.0-rc.1",
|
||||
"@types/react-dom": "npm:types-react-dom@19.0.0-rc.1",
|
||||
"lucide-react": "^0.468.0",
|
||||
@@ -43,9 +43,9 @@
|
||||
"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",
|
||||
"sonner": "^1.7.0",
|
||||
"sonner": "^1.7.1",
|
||||
"zod": "^3.23.8"
|
||||
},
|
||||
"prettier": "@kit/prettier-config",
|
||||
|
||||
@@ -148,7 +148,7 @@ export function PersonalAccountDropdown({
|
||||
|
||||
<DropdownMenuItem asChild>
|
||||
<Link
|
||||
className={'s-full flex items-center space-x-2'}
|
||||
className={'s-full flex items-center space-x-2 cursor-pointer'}
|
||||
href={paths.home}
|
||||
>
|
||||
<Home className={'h-5'} />
|
||||
@@ -162,7 +162,7 @@ export function PersonalAccountDropdown({
|
||||
<DropdownMenuSeparator />
|
||||
|
||||
<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'} />
|
||||
|
||||
<span>
|
||||
@@ -176,7 +176,7 @@ export function PersonalAccountDropdown({
|
||||
|
||||
<DropdownMenuItem asChild>
|
||||
<Link
|
||||
className={'s-full flex items-center space-x-2'}
|
||||
className={'s-full flex items-center space-x-2 cursor-pointer'}
|
||||
href={'/admin'}
|
||||
>
|
||||
<Shield className={'h-5'} />
|
||||
|
||||
@@ -21,15 +21,15 @@
|
||||
"@kit/ui": "workspace:*",
|
||||
"@makerkit/data-loader-supabase-core": "^0.0.8",
|
||||
"@makerkit/data-loader-supabase-nextjs": "^1.2.3",
|
||||
"@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",
|
||||
"@types/react": "npm:types-react@19.0.0-rc.1",
|
||||
"lucide-react": "^0.468.0",
|
||||
"next": "15.0.4",
|
||||
"react": "19.0.0",
|
||||
"react-dom": "19.0.0",
|
||||
"react-hook-form": "^7.53.2",
|
||||
"react-hook-form": "^7.54.0",
|
||||
"zod": "^3.23.8"
|
||||
},
|
||||
"exports": {
|
||||
|
||||
@@ -28,14 +28,14 @@
|
||||
"@kit/ui": "workspace:*",
|
||||
"@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",
|
||||
"@types/react": "npm:types-react@19.0.0-rc.1",
|
||||
"lucide-react": "^0.468.0",
|
||||
"next": "15.0.4",
|
||||
"react-hook-form": "^7.53.2",
|
||||
"react-hook-form": "^7.54.0",
|
||||
"react-i18next": "^15.1.3",
|
||||
"sonner": "^1.7.0",
|
||||
"sonner": "^1.7.1",
|
||||
"zod": "^3.23.8"
|
||||
},
|
||||
"prettier": "@kit/prettier-config",
|
||||
|
||||
@@ -20,8 +20,8 @@
|
||||
"@kit/tailwind-config": "workspace:*",
|
||||
"@kit/tsconfig": "workspace:*",
|
||||
"@kit/ui": "workspace:*",
|
||||
"@supabase/supabase-js": "^2.47.1",
|
||||
"@tanstack/react-query": "5.62.2",
|
||||
"@supabase/supabase-js": "^2.47.2",
|
||||
"@tanstack/react-query": "5.62.3",
|
||||
"@types/react": "npm:types-react@19.0.0-rc.1",
|
||||
"lucide-react": "^0.468.0",
|
||||
"react": "19.0.0",
|
||||
|
||||
@@ -32,8 +32,8 @@
|
||||
"@kit/tailwind-config": "workspace:*",
|
||||
"@kit/tsconfig": "workspace:*",
|
||||
"@kit/ui": "workspace:*",
|
||||
"@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",
|
||||
"@types/react": "npm:types-react@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",
|
||||
"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",
|
||||
"sonner": "^1.7.0",
|
||||
"sonner": "^1.7.1",
|
||||
"zod": "^3.23.8"
|
||||
},
|
||||
"prettier": "@kit/prettier-config",
|
||||
|
||||
@@ -21,7 +21,7 @@
|
||||
"@kit/shared": "workspace:*",
|
||||
"@kit/tailwind-config": "workspace:*",
|
||||
"@kit/tsconfig": "workspace:*",
|
||||
"@tanstack/react-query": "5.62.2",
|
||||
"@tanstack/react-query": "5.62.3",
|
||||
"next": "15.0.4",
|
||||
"react": "19.0.0",
|
||||
"react-dom": "19.0.0",
|
||||
@@ -29,7 +29,7 @@
|
||||
},
|
||||
"dependencies": {
|
||||
"i18next": "24.0.5",
|
||||
"i18next-browser-languagedetector": "8.0.0",
|
||||
"i18next-browser-languagedetector": "8.0.1",
|
||||
"i18next-resources-to-backend": "^1.2.1"
|
||||
},
|
||||
"eslintConfig": {
|
||||
|
||||
@@ -21,7 +21,7 @@
|
||||
"@kit/supabase": "workspace:*",
|
||||
"@kit/tailwind-config": "workspace:*",
|
||||
"@kit/tsconfig": "workspace:*",
|
||||
"@supabase/supabase-js": "^2.47.1",
|
||||
"@supabase/supabase-js": "^2.47.2",
|
||||
"next": "15.0.4",
|
||||
"zod": "^3.23.8"
|
||||
},
|
||||
|
||||
@@ -29,8 +29,8 @@
|
||||
"@kit/tailwind-config": "workspace:*",
|
||||
"@kit/tsconfig": "workspace:*",
|
||||
"@supabase/ssr": "^0.5.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",
|
||||
"@types/react": "npm:types-react@19.0.0-rc.1",
|
||||
"next": "15.0.4",
|
||||
"react": "19.0.0",
|
||||
|
||||
@@ -43,7 +43,7 @@
|
||||
"@kit/tailwind-config": "workspace:*",
|
||||
"@kit/tsconfig": "workspace:*",
|
||||
"@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",
|
||||
"@types/react": "npm:types-react@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",
|
||||
"prettier": "^3.4.2",
|
||||
"react-day-picker": "^8.10.1",
|
||||
"react-hook-form": "^7.53.2",
|
||||
"react-hook-form": "^7.54.0",
|
||||
"react-i18next": "^15.1.3",
|
||||
"sonner": "^1.7.0",
|
||||
"sonner": "^1.7.1",
|
||||
"tailwindcss": "3.4.16",
|
||||
"tailwindcss-animate": "^1.0.7",
|
||||
"typescript": "^5.7.2",
|
||||
@@ -106,6 +106,7 @@
|
||||
"./chart": "./src/shadcn/chart.tsx",
|
||||
"./skeleton": "./src/shadcn/skeleton.tsx",
|
||||
"./shadcn-sidebar": "./src/shadcn/sidebar.tsx",
|
||||
"./collapsible": "./src/shadcn/collapsible.tsx",
|
||||
"./utils": "./src/lib/utils/index.ts",
|
||||
"./if": "./src/makerkit/if.tsx",
|
||||
"./trans": "./src/makerkit/trans.tsx",
|
||||
|
||||
@@ -30,7 +30,7 @@ export const FeatureCard = forwardRef<HTMLDivElement, FeatureCardProps>(
|
||||
>
|
||||
<CardHeader>
|
||||
<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}
|
||||
</CardDescription>
|
||||
</CardHeader>
|
||||
|
||||
@@ -10,7 +10,7 @@ export const FeatureGrid = forwardRef<
|
||||
<div
|
||||
ref={ref}
|
||||
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,
|
||||
)}
|
||||
{...props}
|
||||
|
||||
@@ -20,7 +20,7 @@ export const FeatureShowcase = forwardRef<HTMLDivElement, FeatureShowcaseProps>(
|
||||
>
|
||||
<div className="flex w-full max-w-5xl flex-col space-y-4">
|
||||
{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}
|
||||
</h3>
|
||||
</div>
|
||||
|
||||
@@ -24,7 +24,7 @@ export const Header = forwardRef<HTMLDivElement, HeaderProps>(
|
||||
>
|
||||
<div className="container">
|
||||
<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="flex items-center justify-end space-x-1">
|
||||
{actions}
|
||||
|
||||
@@ -16,7 +16,7 @@ export const HeroTitle = forwardRef<
|
||||
<Comp
|
||||
ref={ref}
|
||||
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,
|
||||
)}
|
||||
{...props}
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
import React from 'react';
|
||||
|
||||
import { cn } from '../../lib/utils';
|
||||
import { Heading } from '../../shadcn/heading';
|
||||
import { HeroTitle } from './hero-title';
|
||||
|
||||
interface HeroProps {
|
||||
@@ -53,12 +52,11 @@ export function Hero({
|
||||
|
||||
{subtitle && (
|
||||
<div className="flex max-w-2xl flex-col space-y-1">
|
||||
<Heading
|
||||
level={3}
|
||||
className="p-0 text-center font-sans text-base font-normal"
|
||||
<h3
|
||||
className="p-0 text-center text-xl font-sans font-normal text-muted-foreground tracking-tight"
|
||||
>
|
||||
{subtitle}
|
||||
</Heading>
|
||||
</h3>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
@@ -78,8 +76,11 @@ export function Hero({
|
||||
|
||||
{image && (
|
||||
<div
|
||||
className={cn('mx-auto flex max-w-[85rem] justify-center py-8', {
|
||||
['delay-300 duration-1000 animate-in fade-in zoom-in-95 slide-in-from-top-32 fill-mode-both']:
|
||||
style={{
|
||||
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,
|
||||
})}
|
||||
>
|
||||
|
||||
@@ -3,7 +3,6 @@ import { forwardRef } from 'react';
|
||||
import { Slot, Slottable } from '@radix-ui/react-slot';
|
||||
|
||||
import { cn } from '../../lib/utils';
|
||||
import { GradientSecondaryText } from './gradient-secondary-text';
|
||||
|
||||
export const Pill = forwardRef<
|
||||
HTMLHeadingElement,
|
||||
@@ -33,7 +32,7 @@ export const Pill = forwardRef<
|
||||
</span>
|
||||
)}
|
||||
<Slottable>
|
||||
<GradientSecondaryText>{props.children}</GradientSecondaryText>
|
||||
<span className={'text-secondary-foreground'}>{props.children}</span>
|
||||
</Slottable>
|
||||
</Comp>
|
||||
);
|
||||
|
||||
@@ -18,7 +18,7 @@ export const SecondaryHero = forwardRef<HTMLDivElement, SecondaryHeroProps>(
|
||||
<div
|
||||
ref={ref}
|
||||
className={cn(
|
||||
'flex flex-col items-center space-y-4 text-center',
|
||||
'flex flex-col items-center space-y-6 text-center',
|
||||
className,
|
||||
)}
|
||||
{...props}
|
||||
@@ -30,12 +30,11 @@ export const SecondaryHero = forwardRef<HTMLDivElement, SecondaryHeroProps>(
|
||||
{heading}
|
||||
</Heading>
|
||||
|
||||
<Heading
|
||||
level={3}
|
||||
className="font-sans font-normal tracking-tight text-muted-foreground"
|
||||
<h3
|
||||
className="font-sans font-normal text-xl tracking-tight text-muted-foreground"
|
||||
>
|
||||
{subheading}
|
||||
</Heading>
|
||||
</h3>
|
||||
</div>
|
||||
|
||||
{children}
|
||||
|
||||
@@ -74,7 +74,7 @@ export function SubMenuModeToggle() {
|
||||
|
||||
return (
|
||||
<DropdownMenuItem
|
||||
className={cn('flex items-center space-x-2', {
|
||||
className={cn('flex items-center space-x-2 cursor-pointer', {
|
||||
'bg-muted': isSelected,
|
||||
})}
|
||||
key={mode}
|
||||
|
||||
458
pnpm-lock.yaml
generated
458
pnpm-lock.yaml
generated
File diff suppressed because it is too large
Load Diff
@@ -9,7 +9,7 @@
|
||||
"typecheck": "tsc --noEmit"
|
||||
},
|
||||
"dependencies": {
|
||||
"@trivago/prettier-plugin-sort-imports": "^4.3.0",
|
||||
"@trivago/prettier-plugin-sort-imports": "^5.1.0",
|
||||
"prettier": "^3.4.2",
|
||||
"prettier-plugin-tailwindcss": "^0.6.9"
|
||||
},
|
||||
|
||||
Reference in New Issue
Block a user