Design Updates (#379)

* Enhance Marketing Pages and UI Components

- Updated the marketing homepage to include an Ecosystem Showcase component, improving the presentation of the SaaS Starter Kit.
- Refined various UI components, including adjustments to spacing, typography, and layout for better visual consistency.
- Improved accessibility by adding aria-labels and ensuring proper semantic structure in components.
- Adjusted styles across multiple components to enhance responsiveness and user experience.
- Updated the pricing table and feature cards to align with the new design standards, ensuring a cohesive look and feel throughout the application.
- Updated plan picker design
This commit is contained in:
Giancarlo Buomprisco
2025-10-02 15:14:11 +08:00
committed by GitHub
parent d8bb7f56df
commit 54d6b4897f
56 changed files with 1014 additions and 1142 deletions

View File

@@ -9,7 +9,7 @@ export function If<Value = unknown>({
}: React.PropsWithoutRef<{
condition: Condition<Value>;
children: React.ReactNode | ((value: Value) => React.ReactNode);
fallback?: React.ReactNode;
fallback?: React.ReactNode | (() => React.ReactNode);
}>) {
return useMemo(() => {
if (condition) {
@@ -21,6 +21,10 @@ export function If<Value = unknown>({
}
if (fallback) {
if (typeof fallback === 'function') {
return <>{fallback()}</>;
}
return <>{fallback}</>;
}

View File

@@ -0,0 +1,61 @@
import React from 'react';
import { cn } from '../../lib/utils';
interface EcosystemShowcaseProps extends React.HTMLAttributes<HTMLDivElement> {
heading: React.ReactNode;
description?: React.ReactNode;
textPosition?: 'left' | 'right';
}
export const EcosystemShowcase: React.FC<EcosystemShowcaseProps> =
function EcosystemShowcaseComponent({
className,
heading,
description,
textPosition = 'left',
children,
...props
}) {
return (
<div
className={cn(
'bg-muted/50 flex flex-1 flex-col space-y-8 rounded-md p-6 lg:space-y-0 lg:space-x-16',
className,
{
'lg:flex-row': textPosition === 'left',
'lg:flex-row-reverse': textPosition === 'right',
},
)}
{...props}
>
<div
className={cn(
'h-full w-full flex-col items-start gap-y-4 text-left lg:w-1/3',
{
'text-right': textPosition === 'right',
},
)}
>
<h2 className="text-secondary-foreground text-3xl font-normal tracking-tight">
{heading}
</h2>
{description && (
<p className="text-muted-foreground mt-2 text-base lg:text-lg">
{description}
</p>
)}
</div>
<div
className={cn(
'flex w-full lg:w-2/3',
textPosition === 'right' && 'm-0 text-right',
)}
>
{children}
</div>
</div>
);
};

View File

@@ -15,9 +15,9 @@ export const FeatureCard: React.FC<FeatureCardProps> = ({
...props
}) => {
return (
<div className={cn('rounded-xl border p-4', className)} {...props}>
<CardHeader>
<CardTitle className="text-xl font-medium">{label}</CardTitle>
<div className={cn('bg-muted/50 rounded', className)} {...props}>
<CardHeader className="p-4">
<CardTitle className="text-lg font-medium">{label}</CardTitle>
<CardDescription className="text-muted-foreground max-w-xs text-sm font-normal">
{description}

View File

@@ -20,12 +20,13 @@ export const FeatureShowcase: React.FC<FeatureShowcaseProps> =
className={cn('flex flex-col justify-between space-y-8', className)}
{...props}
>
<div className="flex w-full max-w-5xl flex-col gap-y-4">
<div className="flex w-full flex-col gap-y-4">
{icon && <div className="flex">{icon}</div>}
<h3 className="text-3xl font-normal tracking-tight xl:text-5xl">
{heading}
</h3>
</div>
{children}
</div>
);
@@ -40,7 +41,7 @@ export function FeatureShowcaseIconContainer(
<div className={'flex'}>
<div
className={cn(
'flex items-center justify-center space-x-4 rounded-lg p-3 font-medium',
'flex items-center justify-center space-x-2.5 font-medium',
props.className,
)}
>

View File

@@ -26,7 +26,7 @@ export const Footer: React.FC<FooterProps> = ({
return (
<footer
className={cn(
'site-footer relative mt-auto w-full py-8 2xl:py-20',
'site-footer bg-muted/20 relative mt-auto w-full py-8 2xl:py-20',
className,
)}
{...props}
@@ -39,9 +39,7 @@ export const Footer: React.FC<FooterProps> = ({
<div className="flex flex-col gap-y-4">
<div>
<p className="text-muted-foreground text-sm tracking-tight">
{description}
</p>
<p className="text-muted-foreground text-sm">{description}</p>
</div>
<div className="text-muted-foreground flex text-xs">
@@ -51,10 +49,10 @@ export const Footer: React.FC<FooterProps> = ({
</div>
</div>
<div className="flex w-full flex-col gap-y-4 lg:flex-row lg:justify-end lg:gap-x-6 lg:gap-y-0 xl:gap-x-12">
<div className="flex w-full flex-1 flex-col gap-y-4 lg:flex-row lg:justify-end lg:gap-x-6 lg:gap-y-0 xl:gap-x-12">
{sections.map((section, index) => (
<div key={index}>
<div className="flex flex-col gap-y-2.5">
<div className="flex flex-col gap-y-1">
<FooterSectionHeading>{section.heading}</FooterSectionHeading>
<FooterSectionList>
@@ -76,14 +74,14 @@ export const Footer: React.FC<FooterProps> = ({
function FooterSectionHeading(props: React.PropsWithChildren) {
return (
<span className="font-heading text-sm font-semibold tracking-tight">
<span className="font-heading text-secondary-foreground/90 text-sm font-medium">
{props.children}
</span>
);
}
function FooterSectionList(props: React.PropsWithChildren) {
return <ul className="flex flex-col gap-y-2">{props.children}</ul>;
return <ul className="flex flex-col gap-y-1">{props.children}</ul>;
}
function FooterLink({
@@ -91,7 +89,7 @@ function FooterLink({
children,
}: React.PropsWithChildren<{ href: string }>) {
return (
<li className="text-muted-foreground text-sm tracking-tight hover:underline [&>a]:transition-colors">
<li className="text-muted-foreground text-sm font-medium hover:underline [&>a]:transition-colors">
<a href={href}>{children}</a>
</li>
);

View File

@@ -16,7 +16,7 @@ export const Header: React.FC<HeaderProps> = function ({
return (
<div
className={cn(
'site-header bg-background/80 dark:bg-background/50 sticky top-0 z-10 w-full py-1 backdrop-blur-md',
'site-header bg-background/80 dark:bg-background/80 sticky top-0 z-10 w-full backdrop-blur-lg',
className,
)}
{...props}

View File

@@ -12,7 +12,7 @@ export const HeroTitle: React.FC<
return (
<Comp
className={cn(
'hero-title flex flex-col text-center font-sans text-4xl font-semibold tracking-tighter sm:text-6xl lg:max-w-5xl lg:text-7xl xl:text-[4.5rem] dark:text-white',
'hero-title flex flex-col text-center font-sans text-4xl font-medium tracking-tighter sm:text-6xl lg:max-w-5xl lg:text-7xl xl:max-w-6xl dark:text-white',
className,
)}
{...props}

View File

@@ -23,7 +23,7 @@ export function Hero({
animate = true,
}: HeroProps) {
return (
<div className={cn('mx-auto flex flex-col space-y-20', className)}>
<div className={cn('mx-auto flex flex-col space-y-14', className)}>
<div
style={{
MozAnimationDuration: '100ms',
@@ -35,7 +35,7 @@ export function Hero({
},
)}
>
<div className="flex w-full flex-1 flex-col items-center gap-y-6 xl:gap-y-8 2xl:gap-y-12">
<div className="flex w-full flex-1 flex-col items-center gap-y-6 xl:gap-y-8">
{pill && (
<div
className={cn({
@@ -51,8 +51,8 @@ export function Hero({
<HeroTitle>{title}</HeroTitle>
{subtitle && (
<div className="flex max-w-lg">
<h3 className="text-muted-foreground p-0 text-center font-sans text-2xl font-normal tracking-tight">
<div className="flex max-w-3xl">
<h3 className="text-secondary-foreground/70 p-0 text-center font-sans text-xl font-medium tracking-tight">
{subtitle}
</h3>
</div>

View File

@@ -13,3 +13,4 @@ export * from './feature-card';
export * from './newsletter-signup';
export * from './newsletter-signup-container';
export * from './coming-soon';
export * from './ecosystem-showcase';

View File

@@ -14,7 +14,7 @@ export const Pill: React.FC<
return (
<Comp
className={cn(
'bg-muted/50 flex items-center gap-x-1.5 rounded-full border px-2 py-1 pr-2 text-center text-sm font-medium text-transparent',
'bg-muted/50 flex min-h-10 items-center gap-x-1.5 rounded-full border px-2 py-1 text-center text-sm font-medium text-transparent',
className,
)}
{...props}

View File

@@ -19,7 +19,7 @@ export const SecondaryHero: React.FC<SecondaryHeroProps> =
return (
<div
className={cn(
'flex flex-col items-center space-y-6 text-center',
'flex flex-col items-center space-y-4 text-center',
className,
)}
{...props}
@@ -31,7 +31,7 @@ export const SecondaryHero: React.FC<SecondaryHeroProps> =
{heading}
</Heading>
<h3 className="text-muted-foreground font-sans text-xl font-normal tracking-tight">
<h3 className="text-secondary-foreground/70 text-center font-sans text-xl font-medium tracking-tight">
{subheading}
</h3>
</div>

View File

@@ -6,24 +6,30 @@ export function Spinner(
}>,
) {
return (
<div role="status">
<div role="status" aria-label="loading">
<svg
aria-hidden="true"
className={cn(
`fill-primary-foreground text-primary dark:fill-primary dark:text-primary/30 h-8 w-8 animate-spin`,
props.className,
'stroke-muted-foreground h-6 w-6 animate-spin',
)}
viewBox="0 0 100 101"
viewBox="0 0 24 24"
fill="none"
xmlns="http://www.w3.org/2000/svg"
>
<path
d="M100 50.5908C100 78.2051 77.6142 100.591 50 100.591C22.3858 100.591 0 78.2051 0 50.5908C0 22.9766 22.3858 0.59082 50 0.59082C77.6142 0.59082 100 22.9766 100 50.5908ZM9.08144 50.5908C9.08144 73.1895 27.4013 91.5094 50 91.5094C72.5987 91.5094 90.9186 73.1895 90.9186 50.5908C90.9186 27.9921 72.5987 9.67226 50 9.67226C27.4013 9.67226 9.08144 27.9921 9.08144 50.5908Z"
fill="currentColor"
/>
<path
d="M93.9676 39.0409C96.393 38.4038 97.8624 35.9116 97.0079 33.5539C95.2932 28.8227 92.871 24.3692 89.8167 20.348C85.8452 15.1192 80.8826 10.7238 75.2124 7.41289C69.5422 4.10194 63.2754 1.94025 56.7698 1.05124C51.7666 0.367541 46.6976 0.446843 41.7345 1.27873C39.2613 1.69328 37.813 4.19778 38.4501 6.62326C39.0873 9.04874 41.5694 10.4717 44.0505 10.1071C47.8511 9.54855 51.7191 9.52689 55.5402 10.0491C60.8642 10.7766 65.9928 12.5457 70.6331 15.2552C75.2735 17.9648 79.3347 21.5619 82.5849 25.841C84.9175 28.9121 86.7997 32.2913 88.1811 35.8758C89.083 38.2158 91.5421 39.6781 93.9676 39.0409Z"
fill="currentFill"
/>
<g clipPath="url(#clip0_9023_61563)">
<path
d="M14.6437 2.05426C11.9803 1.2966 9.01686 1.64245 6.50315 3.25548C1.85499 6.23817 0.504864 12.4242 3.48756 17.0724C6.47025 21.7205 12.6563 23.0706 17.3044 20.088C20.4971 18.0393 22.1338 14.4793 21.8792 10.9444"
stroke="stroke-current"
strokeWidth="1.4"
strokeLinecap="round"
/>
</g>
<defs>
<clipPath id="clip0_9023_61563">
<rect width="24" height="24" fill="white" />
</clipPath>
</defs>
</svg>
</div>
);