Add Analytics package (#46)
* Add Analytics package Created a new analytics package with a manager to handle tracking of events and page views. The package includes a default provider that can be switched out and uses a NullAnalyticsService if no provider is registered. Additional types, scripts, and package configuration are also provided to support development. * Add marketing components for UI package Introduced new React components under "marketing" for the UI package. These include 'Pill', 'GradientSecondaryText', 'Hero', 'CtaButton', 'FeatureCard', 'FeatureGrid', 'FeatureShowcase', 'GradientText', 'Header', and 'SecondaryHero'. Updated 'package.json' to export these components. Replaced the implementation of 'Home', 'SiteHeader', and 'SiteFooter' with these components for cleaner code and better reusability.
This commit is contained in:
committed by
GitHub
parent
5ee7bacb2a
commit
86d82d889c
23
packages/ui/src/makerkit/marketing/cta-button.tsx
Normal file
23
packages/ui/src/makerkit/marketing/cta-button.tsx
Normal file
@@ -0,0 +1,23 @@
|
||||
import { forwardRef } from 'react';
|
||||
|
||||
import { Button } from '../../shadcn/button';
|
||||
import { cn } from '../../utils';
|
||||
|
||||
export const CtaButton = forwardRef<
|
||||
HTMLButtonElement,
|
||||
React.ComponentProps<typeof Button>
|
||||
>(function CtaButtonComponent({ className, children, ...props }, ref) {
|
||||
return (
|
||||
<Button
|
||||
className={cn(
|
||||
'h-12 rounded-xl px-4 text-base font-semibold transition-all hover:shadow-2xl dark:shadow-primary/30',
|
||||
className,
|
||||
)}
|
||||
asChild
|
||||
ref={ref}
|
||||
{...props}
|
||||
>
|
||||
{children}
|
||||
</Button>
|
||||
);
|
||||
});
|
||||
44
packages/ui/src/makerkit/marketing/feature-card.tsx
Normal file
44
packages/ui/src/makerkit/marketing/feature-card.tsx
Normal file
@@ -0,0 +1,44 @@
|
||||
import React, { forwardRef } from 'react';
|
||||
|
||||
import {
|
||||
CardContent,
|
||||
CardDescription,
|
||||
CardHeader,
|
||||
CardTitle,
|
||||
} from '../../shadcn/card';
|
||||
import { cn } from '../../utils';
|
||||
|
||||
interface FeatureCardProps extends React.HTMLAttributes<HTMLDivElement> {
|
||||
label: string;
|
||||
description: string;
|
||||
image?: React.ReactNode;
|
||||
}
|
||||
|
||||
export const FeatureCard = forwardRef<HTMLDivElement, FeatureCardProps>(
|
||||
function FeatureCardComponent(
|
||||
{ className, label, description, image, children, ...props },
|
||||
ref,
|
||||
) {
|
||||
return (
|
||||
<div
|
||||
ref={ref}
|
||||
className={cn(
|
||||
'rounded-3xl p-2 ring-2 ring-gray-100 dark:ring-primary/10',
|
||||
className,
|
||||
)}
|
||||
{...props}
|
||||
>
|
||||
<CardHeader>
|
||||
<CardTitle className="text-xl font-semibold">{label}</CardTitle>
|
||||
<CardDescription className="max-w-xs text-sm font-semibold tracking-tight text-current">
|
||||
{description}
|
||||
</CardDescription>
|
||||
</CardHeader>
|
||||
<CardContent>
|
||||
{image}
|
||||
{children}
|
||||
</CardContent>
|
||||
</div>
|
||||
);
|
||||
},
|
||||
);
|
||||
21
packages/ui/src/makerkit/marketing/feature-grid.tsx
Normal file
21
packages/ui/src/makerkit/marketing/feature-grid.tsx
Normal file
@@ -0,0 +1,21 @@
|
||||
import React, { forwardRef } from 'react';
|
||||
|
||||
import { cn } from '../../utils';
|
||||
|
||||
export const FeatureGrid = forwardRef<
|
||||
HTMLDivElement,
|
||||
React.HTMLAttributes<HTMLDivElement>
|
||||
>(function FeatureGridComponent({ className, children, ...props }, ref) {
|
||||
return (
|
||||
<div
|
||||
ref={ref}
|
||||
className={cn(
|
||||
'grid w-full grid-cols-1 gap-6 space-y-0 lg:grid-cols-3',
|
||||
className,
|
||||
)}
|
||||
{...props}
|
||||
>
|
||||
{children}
|
||||
</div>
|
||||
);
|
||||
});
|
||||
50
packages/ui/src/makerkit/marketing/feature-showcase.tsx
Normal file
50
packages/ui/src/makerkit/marketing/feature-showcase.tsx
Normal file
@@ -0,0 +1,50 @@
|
||||
import React, { forwardRef } from 'react';
|
||||
|
||||
import { cn } from '../../utils';
|
||||
|
||||
interface FeatureShowcaseProps extends React.HTMLAttributes<HTMLDivElement> {
|
||||
heading: React.ReactNode;
|
||||
icon?: React.ReactNode;
|
||||
}
|
||||
|
||||
export const FeatureShowcase = forwardRef<HTMLDivElement, FeatureShowcaseProps>(
|
||||
function FeatureShowcaseComponent(
|
||||
{ className, heading, icon, children, ...props },
|
||||
ref,
|
||||
) {
|
||||
return (
|
||||
<div
|
||||
ref={ref}
|
||||
className={cn('flex flex-col justify-between space-y-8', className)}
|
||||
{...props}
|
||||
>
|
||||
<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">
|
||||
{heading}
|
||||
</h3>
|
||||
</div>
|
||||
{children}
|
||||
</div>
|
||||
);
|
||||
},
|
||||
);
|
||||
|
||||
export function FeatureShowcaseIconContainer(
|
||||
props: React.PropsWithChildren<{
|
||||
className?: string;
|
||||
}>,
|
||||
) {
|
||||
return (
|
||||
<div className={'flex'}>
|
||||
<div
|
||||
className={cn(
|
||||
'flex items-center justify-center space-x-4 rounded-lg p-3 font-semibold',
|
||||
props.className,
|
||||
)}
|
||||
>
|
||||
{props.children}
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
95
packages/ui/src/makerkit/marketing/footer.tsx
Normal file
95
packages/ui/src/makerkit/marketing/footer.tsx
Normal file
@@ -0,0 +1,95 @@
|
||||
import { forwardRef } from 'react';
|
||||
|
||||
import { cn } from '../../utils';
|
||||
|
||||
interface FooterSection {
|
||||
heading: React.ReactNode;
|
||||
links: Array<{
|
||||
href: string;
|
||||
label: React.ReactNode;
|
||||
}>;
|
||||
}
|
||||
|
||||
interface FooterProps extends React.HTMLAttributes<HTMLElement> {
|
||||
logo: React.ReactNode;
|
||||
description: React.ReactNode;
|
||||
copyright: React.ReactNode;
|
||||
sections: FooterSection[];
|
||||
}
|
||||
|
||||
export const Footer = forwardRef<HTMLElement, FooterProps>(
|
||||
function MarketingFooterComponent(
|
||||
{ className, logo, description, copyright, sections, ...props },
|
||||
ref,
|
||||
) {
|
||||
return (
|
||||
<footer
|
||||
ref={ref}
|
||||
className={cn(
|
||||
'site-footer relative mt-auto w-full py-8 2xl:py-16',
|
||||
className,
|
||||
)}
|
||||
{...props}
|
||||
>
|
||||
<div className="container">
|
||||
<div className="flex flex-col space-y-8 lg:flex-row lg:space-y-0">
|
||||
<div className="flex w-full space-x-2 lg:w-4/12 xl:w-4/12 xl:space-x-6 2xl:space-x-8">
|
||||
<div className="flex flex-col space-y-4">
|
||||
<div>{logo}</div>
|
||||
<div className="flex flex-col space-y-4">
|
||||
<div>
|
||||
<p className="text-sm text-muted-foreground">
|
||||
{description}
|
||||
</p>
|
||||
</div>
|
||||
<div className="flex text-xs text-muted-foreground">
|
||||
<p>{copyright}</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="flex w-full flex-col space-y-8 lg:flex-row lg:justify-end lg:space-x-6 lg:space-y-0 xl:space-x-16">
|
||||
{sections.map((section, index) => (
|
||||
<div key={index}>
|
||||
<div className="flex flex-col space-y-2.5">
|
||||
<FooterSectionHeading>
|
||||
{section.heading}
|
||||
</FooterSectionHeading>
|
||||
|
||||
<FooterSectionList>
|
||||
{section.links.map((link, linkIndex) => (
|
||||
<FooterLink key={linkIndex} href={link.href}>
|
||||
{link.label}
|
||||
</FooterLink>
|
||||
))}
|
||||
</FooterSectionList>
|
||||
</div>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</footer>
|
||||
);
|
||||
},
|
||||
);
|
||||
|
||||
function FooterSectionHeading(props: React.PropsWithChildren) {
|
||||
return <span className="font-heading">{props.children}</span>;
|
||||
}
|
||||
|
||||
function FooterSectionList(props: React.PropsWithChildren) {
|
||||
return <ul className="flex flex-col space-y-2.5">{props.children}</ul>;
|
||||
}
|
||||
|
||||
function FooterLink({
|
||||
href,
|
||||
children,
|
||||
}: React.PropsWithChildren<{ href: string }>) {
|
||||
return (
|
||||
<li className="text-sm text-muted-foreground hover:underline [&>a]:transition-colors">
|
||||
<a href={href}>{children}</a>
|
||||
</li>
|
||||
);
|
||||
}
|
||||
@@ -0,0 +1,27 @@
|
||||
import { forwardRef } from 'react';
|
||||
|
||||
import { Slot, Slottable } from '@radix-ui/react-slot';
|
||||
|
||||
import { cn } from '../../utils';
|
||||
|
||||
export const GradientSecondaryText = forwardRef<
|
||||
HTMLSpanElement,
|
||||
React.HTMLAttributes<HTMLSpanElement> & {
|
||||
asChild?: boolean;
|
||||
}
|
||||
>(function GradientSecondaryTextComponent({ className, ...props }, ref) {
|
||||
const Comp = props.asChild ? Slot : 'span';
|
||||
|
||||
return (
|
||||
<Comp
|
||||
ref={ref}
|
||||
className={cn(
|
||||
'bg-gradient-to-r from-foreground/50 to-foreground bg-clip-text text-transparent',
|
||||
className,
|
||||
)}
|
||||
{...props}
|
||||
>
|
||||
<Slottable>{props.children}</Slottable>
|
||||
</Comp>
|
||||
);
|
||||
});
|
||||
21
packages/ui/src/makerkit/marketing/gradient-text.tsx
Normal file
21
packages/ui/src/makerkit/marketing/gradient-text.tsx
Normal file
@@ -0,0 +1,21 @@
|
||||
import React, { forwardRef } from 'react';
|
||||
|
||||
import { cn } from '../../utils';
|
||||
|
||||
export const GradientText = forwardRef<
|
||||
HTMLSpanElement,
|
||||
React.HTMLAttributes<HTMLSpanElement>
|
||||
>(function GradientTextComponent({ className, children, ...props }, ref) {
|
||||
return (
|
||||
<span
|
||||
ref={ref}
|
||||
className={cn(
|
||||
'bg-gradient-to-r bg-clip-text text-transparent',
|
||||
className,
|
||||
)}
|
||||
{...props}
|
||||
>
|
||||
{children}
|
||||
</span>
|
||||
);
|
||||
});
|
||||
37
packages/ui/src/makerkit/marketing/header.tsx
Normal file
37
packages/ui/src/makerkit/marketing/header.tsx
Normal file
@@ -0,0 +1,37 @@
|
||||
import { forwardRef } from 'react';
|
||||
|
||||
import { cn } from '../../utils';
|
||||
|
||||
interface HeaderProps extends React.HTMLAttributes<HTMLDivElement> {
|
||||
logo?: React.ReactNode;
|
||||
navigation?: React.ReactNode;
|
||||
actions?: React.ReactNode;
|
||||
}
|
||||
|
||||
export const Header = forwardRef<HTMLDivElement, HeaderProps>(
|
||||
function MarketingHeaderComponent(
|
||||
{ className, logo, navigation, actions, ...props },
|
||||
ref,
|
||||
) {
|
||||
return (
|
||||
<div
|
||||
ref={ref}
|
||||
className={cn(
|
||||
'site-header sticky top-0 z-10 w-full bg-background/80 py-2 backdrop-blur-md dark:bg-background/50',
|
||||
className,
|
||||
)}
|
||||
{...props}
|
||||
>
|
||||
<div className="container">
|
||||
<div className="grid h-14 grid-cols-3 items-center">
|
||||
<div>{logo}</div>
|
||||
<div className="order-first md:order-none">{navigation}</div>
|
||||
<div className="flex items-center justify-end space-x-1">
|
||||
{actions}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
},
|
||||
);
|
||||
27
packages/ui/src/makerkit/marketing/hero-title.tsx
Normal file
27
packages/ui/src/makerkit/marketing/hero-title.tsx
Normal file
@@ -0,0 +1,27 @@
|
||||
import { forwardRef } from 'react';
|
||||
|
||||
import { Slot, Slottable } from '@radix-ui/react-slot';
|
||||
|
||||
import { cn } from '../../utils';
|
||||
|
||||
export const HeroTitle = forwardRef<
|
||||
HTMLHeadingElement,
|
||||
React.HTMLAttributes<HTMLHeadingElement> & {
|
||||
asChild?: boolean;
|
||||
}
|
||||
>(function HeroTitleComponent({ children, className, ...props }, ref) {
|
||||
const Comp = props.asChild ? Slot : 'h1';
|
||||
|
||||
return (
|
||||
<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]',
|
||||
className,
|
||||
)}
|
||||
{...props}
|
||||
>
|
||||
<Slottable>{children}</Slottable>
|
||||
</Comp>
|
||||
);
|
||||
});
|
||||
89
packages/ui/src/makerkit/marketing/hero.tsx
Normal file
89
packages/ui/src/makerkit/marketing/hero.tsx
Normal file
@@ -0,0 +1,89 @@
|
||||
import React from 'react';
|
||||
|
||||
import { Heading } from '../../shadcn/heading';
|
||||
import { cn } from '../../utils';
|
||||
import { HeroTitle } from './hero-title';
|
||||
|
||||
interface HeroProps {
|
||||
pill?: React.ReactNode;
|
||||
title: React.ReactNode;
|
||||
subtitle?: React.ReactNode;
|
||||
cta?: React.ReactNode;
|
||||
image?: React.ReactNode;
|
||||
className?: string;
|
||||
animate?: boolean;
|
||||
}
|
||||
|
||||
export function Hero({
|
||||
pill,
|
||||
title,
|
||||
subtitle,
|
||||
cta,
|
||||
image,
|
||||
className,
|
||||
animate = true,
|
||||
}: HeroProps) {
|
||||
return (
|
||||
<div className={cn('mx-auto flex flex-col space-y-20', className)}>
|
||||
<div
|
||||
className={cn(
|
||||
'mx-auto flex flex-1 flex-col items-center justify-center md:flex-row',
|
||||
{
|
||||
['duration-1000 animate-in fade-in zoom-in-90 slide-in-from-top-36']:
|
||||
animate,
|
||||
},
|
||||
)}
|
||||
>
|
||||
<div className="flex w-full flex-1 flex-col items-center space-y-6 xl:space-y-8 2xl:space-y-10">
|
||||
{pill && (
|
||||
<div
|
||||
className={cn({
|
||||
['delay-300 duration-700 animate-in fade-in fill-mode-both']:
|
||||
animate,
|
||||
})}
|
||||
>
|
||||
{pill}
|
||||
</div>
|
||||
)}
|
||||
|
||||
<div className="flex flex-col items-center space-y-8">
|
||||
<HeroTitle>{title}</HeroTitle>
|
||||
|
||||
{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"
|
||||
>
|
||||
{subtitle}
|
||||
</Heading>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
|
||||
{cta && (
|
||||
<div
|
||||
className={cn({
|
||||
['delay-500 duration-1000 animate-in fade-in fill-mode-both']:
|
||||
animate,
|
||||
})}
|
||||
>
|
||||
{cta}
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{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']:
|
||||
animate,
|
||||
})}
|
||||
>
|
||||
{image}
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
12
packages/ui/src/makerkit/marketing/index.tsx
Normal file
12
packages/ui/src/makerkit/marketing/index.tsx
Normal file
@@ -0,0 +1,12 @@
|
||||
export * from './hero-title';
|
||||
export * from './pill';
|
||||
export * from './gradient-secondary-text';
|
||||
export * from './gradient-text';
|
||||
export * from './hero';
|
||||
export * from './secondary-hero';
|
||||
export * from './cta-button';
|
||||
export * from './header';
|
||||
export * from './footer';
|
||||
export * from './feature-showcase';
|
||||
export * from './feature-grid';
|
||||
export * from './feature-card';
|
||||
40
packages/ui/src/makerkit/marketing/pill.tsx
Normal file
40
packages/ui/src/makerkit/marketing/pill.tsx
Normal file
@@ -0,0 +1,40 @@
|
||||
import { forwardRef } from 'react';
|
||||
|
||||
import { Slot, Slottable } from '@radix-ui/react-slot';
|
||||
|
||||
import { cn } from '../../utils';
|
||||
import { GradientSecondaryText } from './gradient-secondary-text';
|
||||
|
||||
export const Pill = forwardRef<
|
||||
HTMLHeadingElement,
|
||||
React.HTMLAttributes<HTMLHeadingElement> & {
|
||||
label?: string;
|
||||
asChild?: boolean;
|
||||
}
|
||||
>(function PillComponent({ className, asChild, ...props }, ref) {
|
||||
const Comp = asChild ? Slot : 'h3';
|
||||
|
||||
return (
|
||||
<Comp
|
||||
ref={ref}
|
||||
className={cn(
|
||||
'space-x-2.5 rounded-full border border-gray-100 px-2 py-2.5 text-center text-sm font-medium text-transparent dark:border-primary/10',
|
||||
className,
|
||||
)}
|
||||
{...props}
|
||||
>
|
||||
{props.label && (
|
||||
<span
|
||||
className={
|
||||
'rounded-2xl bg-primary px-2.5 py-1.5 text-sm font-semibold text-primary-foreground'
|
||||
}
|
||||
>
|
||||
{props.label}
|
||||
</span>
|
||||
)}
|
||||
<Slottable>
|
||||
<GradientSecondaryText>{props.children}</GradientSecondaryText>
|
||||
</Slottable>
|
||||
</Comp>
|
||||
);
|
||||
});
|
||||
49
packages/ui/src/makerkit/marketing/secondary-hero.tsx
Normal file
49
packages/ui/src/makerkit/marketing/secondary-hero.tsx
Normal file
@@ -0,0 +1,49 @@
|
||||
import { forwardRef } from 'react';
|
||||
|
||||
import { Heading } from '../../shadcn/heading';
|
||||
import { cn } from '../../utils';
|
||||
|
||||
interface SecondaryHeroProps extends React.HTMLAttributes<HTMLDivElement> {
|
||||
pill?: React.ReactNode;
|
||||
heading: React.ReactNode;
|
||||
subheading: React.ReactNode;
|
||||
}
|
||||
|
||||
export const SecondaryHero = forwardRef<HTMLDivElement, SecondaryHeroProps>(
|
||||
function SecondaryHeroComponent(
|
||||
{ className, pill, heading, subheading, children, ...props },
|
||||
ref,
|
||||
) {
|
||||
return (
|
||||
<div
|
||||
ref={ref}
|
||||
className={cn(
|
||||
'flex flex-col items-center space-y-4 text-center',
|
||||
className,
|
||||
)}
|
||||
{...props}
|
||||
>
|
||||
{pill && (
|
||||
<div className="delay-300 duration-700 animate-in fade-in">
|
||||
{pill}
|
||||
</div>
|
||||
)}
|
||||
|
||||
<div className="flex flex-col">
|
||||
<Heading level={2} className="tracking-tighter">
|
||||
{heading}
|
||||
</Heading>
|
||||
|
||||
<Heading
|
||||
level={3}
|
||||
className="font-sans font-normal tracking-tight text-muted-foreground"
|
||||
>
|
||||
{subheading}
|
||||
</Heading>
|
||||
</div>
|
||||
|
||||
{children}
|
||||
</div>
|
||||
);
|
||||
},
|
||||
);
|
||||
Reference in New Issue
Block a user