refactor(docs): simplify previews and polish navigation/layout styling

This commit is contained in:
Anmoldeep Singh
2026-04-04 00:18:19 +05:30
parent 24cb16a673
commit c55f888520
45 changed files with 360 additions and 425 deletions

File diff suppressed because one or more lines are too long

View File

@@ -9,7 +9,7 @@ import { FlyToExample } from "./examples/flyto-example";
export function ExamplesGrid() {
return (
<div className="animate-fade-in grid grid-cols-1 gap-5 delay-400 sm:grid-cols-2 lg:grid-cols-4">
<div className="grid grid-cols-1 gap-5 sm:grid-cols-2 lg:grid-cols-4">
<AnalyticsExample />
<TrailExample />
<FlyToExample />

View File

@@ -20,36 +20,18 @@ const analyticsData = [
export function AnalyticsExample() {
return (
<ExampleCard
label=""
className="aspect-square sm:col-span-2 sm:aspect-video lg:aspect-auto"
delay="delay-400"
stagger={4}
>
<div className="absolute top-3 left-3 z-10 bg-background/95 backdrop-blur-md rounded-lg p-3 border border-border/50 shadow-lg">
<div className="tracking-wider text-[10px] text-muted-foreground uppercase mb-1">
<div className="bg-background/95 border-border/50 absolute top-3 left-3 z-10 rounded-lg border p-3 shadow-lg backdrop-blur-md">
<div className="text-muted-foreground mb-1 text-[10px] tracking-wider uppercase">
Active Users
</div>
<div className="text-2xl font-semibold leading-tight">2,847</div>
<div className="flex items-center gap-1 mt-1">
<div className="text-2xl leading-tight font-semibold">2,847</div>
<div className="mt-1 flex items-center gap-1">
<TrendingUp className="size-3 text-emerald-500" />
<span className="text-xs text-emerald-500">+12.5%</span>
<span className="text-xs text-muted-foreground">vs last hour</span>
</div>
</div>
<div className="absolute bottom-3 left-3 z-10 bg-background/95 backdrop-blur-md rounded-lg px-3 py-2 border border-border/50 shadow-lg">
<div className="flex items-center gap-4 text-[10px]">
<div className="flex items-center gap-1.5">
<div className="size-3 rounded-full bg-emerald-500" />
<span className="text-muted-foreground">High</span>
</div>
<div className="flex items-center gap-1.5">
<div className="size-2 rounded-full bg-emerald-500" />
<span className="text-muted-foreground">Medium</span>
</div>
<div className="flex items-center gap-1.5">
<div className="size-1.5 rounded-full bg-emerald-500" />
<span className="text-muted-foreground">Low</span>
</div>
<span className="text-muted-foreground text-xs">vs last hour</span>
</div>
</div>
@@ -66,7 +48,7 @@ export function AnalyticsExample() {
}}
/>
<div
className="absolute rounded-full bg-emerald-500/40 animate-ping"
className="absolute animate-ping rounded-full bg-emerald-500/40"
style={{
width: loc.size * 1.5,
height: loc.size * 1.5,
@@ -82,10 +64,10 @@ export function AnalyticsExample() {
<MarkerTooltip>
<div className="text-center">
<div className="font-medium">{loc.city}</div>
<div className="text-emerald-500 font-semibold">
<div className="font-semibold text-emerald-500">
{loc.users}
</div>
<div className="text-[10px] text-muted-foreground">
<div className="text-background/70 text-[10px]">
active users
</div>
</div>

View File

@@ -18,14 +18,14 @@ const home = { lng: -0.07, lat: 51.51 };
export function DeliveryExample() {
const [route, setRoute] = useState<[number, number][]>([]);
const [truckPosition, setTruckPosition] = useState<[number, number] | null>(
null
null,
);
useEffect(() => {
async function fetchRoute() {
try {
const response = await fetch(
`https://router.project-osrm.org/route/v1/driving/${store.lng},${store.lat};${home.lng},${home.lat}?overview=full&geometries=geojson`
`https://router.project-osrm.org/route/v1/driving/${store.lng},${store.lat};${home.lng},${home.lat}?overview=full&geometries=geojson`,
);
const data = await response.json();
@@ -48,7 +48,7 @@ export function DeliveryExample() {
<ExampleCard
label="Delivery"
className="aspect-square sm:col-span-2 sm:aspect-video lg:aspect-auto"
delay="delay-900"
stagger={9}
>
<Map center={[-0.105, 51.511]} zoom={12.4}>
{route.length > 0 && (
@@ -56,14 +56,14 @@ export function DeliveryExample() {
)}
<MapMarker longitude={store.lng} latitude={store.lat}>
<MarkerContent>
<div className="size-3.5 rounded-full bg-emerald-500 border-2 border-white shadow-lg" />
<div className="size-3.5 rounded-full border-2 border-white bg-emerald-500 shadow-lg" />
<MarkerLabel>Store</MarkerLabel>
</MarkerContent>
</MapMarker>
{truckPosition && (
<MapMarker longitude={truckPosition[0]} latitude={truckPosition[1]}>
<MarkerContent>
<div className="bg-blue-500 rounded-full p-1.5 shadow-lg">
<div className="rounded-full bg-blue-500 p-1.5 shadow-lg">
<Truck className="size-3 text-white" />
</div>
</MarkerContent>
@@ -72,7 +72,7 @@ export function DeliveryExample() {
)}
<MapMarker longitude={home.lng} latitude={home.lat}>
<MarkerContent>
<div className="size-3.5 rounded-full bg-blue-500 border-2 border-white shadow-lg" />
<div className="size-3.5 rounded-full border-2 border-white bg-blue-500 shadow-lg" />
<MarkerLabel>Home</MarkerLabel>
</MarkerContent>
</MapMarker>

View File

@@ -117,11 +117,7 @@ const statusConfig: Record<
export function EVChargingExample() {
return (
<ExampleCard
label="EV Charging"
className="aspect-square"
delay="delay-700"
>
<ExampleCard label="EV Charging" className="aspect-square" stagger={7}>
<Map center={[-122.434, 37.776]} zoom={11}>
{stations.map((station) => {
const config = statusConfig[station.status];
@@ -144,7 +140,7 @@ export function EVChargingExample() {
<span className={config.textClass}>{config.label}</span>
</div>
{station.detail && (
<div className="text-muted-foreground">
<div className="text-background/60 text-[11px]">
{station.detail}
</div>
)}

View File

@@ -1,27 +1,32 @@
"use client";
import { CSSProperties } from "react";
import { cn } from "@/lib/utils";
interface ExampleCardProps {
label: string;
label?: string;
className?: string;
delay?: string;
stagger?: number;
children: React.ReactNode;
}
export function ExampleCard({
label,
className,
delay = "delay-500",
stagger = 5,
children,
}: ExampleCardProps) {
return (
<div
className={cn(
"bg-card border-border/50 animate-scale-in relative overflow-hidden rounded-xl border shadow-sm",
delay,
"bg-card border-border/50 animate-scale-in animate-stagger relative overflow-hidden rounded-xl border shadow-sm",
className,
)}
style={
{
"--stagger": stagger,
} as CSSProperties
}
>
{label && (
<div className="text-muted-foreground bg-background/90 absolute top-2 left-2 z-10 rounded px-2 py-1 text-[10px] tracking-wider uppercase backdrop-blur-sm">

View File

@@ -23,7 +23,7 @@ export function FlyToExample() {
const mapRef = useRef<MapRef>(null);
return (
<ExampleCard label="Fly To" className="aspect-square" delay="delay-600">
<ExampleCard label="Fly To" className="aspect-square" stagger={6}>
<Map
center={destination.startCenter}
zoom={0.5}
@@ -36,14 +36,14 @@ export function FlyToExample() {
>
<MarkerContent>
<div className="relative flex items-center justify-center">
<div className="absolute size-6 rounded-full bg-cyan-500/20 animate-ping" />
<div className="size-4 rounded-full bg-cyan-500 border-2 border-white shadow-lg" />
<div className="absolute size-6 animate-ping rounded-full bg-cyan-500/20" />
<div className="size-4 rounded-full border-2 border-white bg-cyan-500 shadow-lg" />
</div>
</MarkerContent>
<MarkerTooltip>
<div className="text-center">
<div className="font-medium">{destination.name}</div>
<div className="text-[10px] text-muted-foreground">
<div className="text-background/70 text-[10px]">
{destination.description}
</div>
</div>

View File

@@ -25,37 +25,37 @@ const end = trailCoordinates[trailCoordinates.length - 1];
export function TrailExample() {
return (
<ExampleCard label="" className="aspect-square" delay="delay-500">
<div className="absolute top-3 left-3 z-10 bg-background/95 backdrop-blur-md rounded-lg p-3 border border-border/50 shadow-lg">
<div className="flex items-center gap-1.5 mb-2">
<ExampleCard className="aspect-square" stagger={5}>
<div className="bg-background/95 border-border/50 absolute top-3 left-3 z-10 rounded-lg border p-3 shadow-lg backdrop-blur-md">
<div className="mb-2 flex items-center gap-1.5">
<Bike className="size-3.5 text-emerald-500" />
<span className="text-xs font-medium">Central Park Loop</span>
</div>
<div className="grid grid-cols-3 gap-3 text-center">
<div>
<div className="flex items-center justify-center gap-1 text-muted-foreground mb-0.5">
<div className="text-muted-foreground mb-0.5 flex items-center justify-center gap-1">
<Route className="size-3" />
</div>
<div className="text-sm font-semibold">6.2</div>
<div className="text-[9px] text-muted-foreground uppercase">
<div className="text-muted-foreground text-[9px] uppercase">
Miles
</div>
</div>
<div>
<div className="flex items-center justify-center gap-1 text-muted-foreground mb-0.5">
<div className="text-muted-foreground mb-0.5 flex items-center justify-center gap-1">
<Clock className="size-3" />
</div>
<div className="text-sm font-semibold">32</div>
<div className="text-[9px] text-muted-foreground uppercase">
<div className="text-muted-foreground text-[9px] uppercase">
Mins
</div>
</div>
<div>
<div className="flex items-center justify-center gap-1 text-muted-foreground mb-0.5">
<div className="text-muted-foreground mb-0.5 flex items-center justify-center gap-1">
<Flame className="size-3" />
</div>
<div className="text-sm font-semibold">285</div>
<div className="text-[9px] text-muted-foreground uppercase">
<div className="text-muted-foreground text-[9px] uppercase">
Cal
</div>
</div>
@@ -72,13 +72,13 @@ export function TrailExample() {
<MapMarker longitude={start[0]} latitude={start[1]}>
<MarkerContent>
<div className="size-3 rounded-full bg-emerald-500 border-2 border-white shadow-lg" />
<div className="size-3 rounded-full border-2 border-white bg-emerald-500 shadow-lg" />
</MarkerContent>
</MapMarker>
<MapMarker longitude={end[0]} latitude={end[1]}>
<MarkerContent>
<div className="size-3 rounded-full bg-red-500 border-2 border-white shadow-lg" />
<div className="size-3 rounded-full border-2 border-white bg-red-500 shadow-lg" />
</MarkerContent>
</MapMarker>
</Map>

View File

@@ -6,63 +6,57 @@ import { ExampleCard } from "./example-card";
export function TrendingExample() {
return (
<ExampleCard label="Trending" className="aspect-square" delay="delay-800">
<ExampleCard label="Trending" className="aspect-square" stagger={8}>
<Map center={[-73.99, 40.735]} zoom={10}>
<MapMarker longitude={-73.9857} latitude={40.7484}>
<MarkerContent>
<div className="relative flex items-center justify-center">
<div className="absolute size-18 rounded-full bg-orange-500/30 pointer-events-none" />
<div className="pointer-events-none absolute size-18 rounded-full bg-orange-500/30" />
<div className="absolute size-7 rounded-full bg-orange-500/40" />
<div className="bg-linear-to-br from-orange-500 to-red-500 rounded-full p-1.5 shadow-lg shadow-orange-500/50">
<div className="rounded-full bg-linear-to-br from-orange-500 to-red-500 p-1.5 shadow-lg shadow-orange-500/50">
<Flame className="size-3.5 text-white" />
</div>
</div>
</MarkerContent>
<MarkerTooltip>
<div className="text-center">
<div className="font-medium">Times Square</div>
<div className="flex items-center justify-center gap-1 text-muted-foreground">
<TrendingUp className="size-3 text-green-500" />
<span className="text-xs text-green-500">2.4k visitors</span>
</div>
<p className="font-medium">Times Square</p>
<div className="flex items-center gap-1">
<TrendingUp className="size-3 text-green-500" />
<span className="text-xs text-green-500">2.4k visitors</span>
</div>
</MarkerTooltip>
</MapMarker>
<MapMarker longitude={-73.9654} latitude={40.7829}>
<MarkerContent>
<div className="relative flex items-center justify-center">
<div className="absolute size-14 rounded-full bg-rose-500/30 pointer-events-none" />
<div className="bg-linear-to-br from-rose-500 to-pink-500 rounded-full p-1.5 shadow-lg shadow-rose-500/50">
<div className="pointer-events-none absolute size-14 rounded-full bg-rose-500/30" />
<div className="rounded-full bg-linear-to-br from-rose-500 to-pink-500 p-1.5 shadow-lg shadow-rose-500/50">
<Flame className="size-3 text-white" />
</div>
</div>
</MarkerContent>
<MarkerTooltip>
<div className="text-center">
<div className="font-medium">Central Park</div>
<div className="flex items-center justify-center gap-1 text-muted-foreground">
<TrendingUp className="size-3 text-green-500" />
<span className="text-xs text-green-500">1.8k visitors</span>
</div>
<p className="font-medium">Central Park</p>
<div className="flex items-center gap-1">
<TrendingUp className="size-3 text-green-500" />
<span className="text-xs text-green-500">1.8k visitors</span>
</div>
</MarkerTooltip>
</MapMarker>
<MapMarker longitude={-74.0445} latitude={40.6892}>
<MarkerContent>
<div className="relative flex items-center justify-center">
<div className="absolute size-12 rounded-full bg-amber-500/30 pointer-events-none" />
<div className="bg-linear-to-br from-amber-500 to-yellow-500 rounded-full p-1 shadow-lg shadow-amber-500/50">
<div className="pointer-events-none absolute size-12 rounded-full bg-amber-500/30" />
<div className="rounded-full bg-linear-to-br from-amber-500 to-yellow-500 p-1 shadow-lg shadow-amber-500/50">
<Flame className="size-2.5 text-white" />
</div>
</div>
</MarkerContent>
<MarkerTooltip>
<div className="text-center">
<div className="font-medium">Statue of Liberty</div>
<div className="flex items-center justify-center gap-1 text-muted-foreground">
<TrendingUp className="size-3 text-green-500" />
<span className="text-xs text-green-500">890 visitors</span>
</div>
<p className="font-medium">Statue of Liberty</p>
<div className="flex items-center gap-1">
<TrendingUp className="size-3 text-green-500" />
<span className="text-xs text-green-500">890 visitors</span>
</div>
</MarkerTooltip>
</MapMarker>

View File

@@ -1,3 +1,6 @@
import Link from "next/link";
import { CSSProperties } from "react";
import { ExamplesGrid } from "./_components/examples-grid";
import { Footer } from "@/components/footer";
import {
@@ -7,7 +10,6 @@ import {
PageActions,
} from "@/components/page-header";
import { Button } from "@/components/ui/button";
import Link from "next/link";
export default function Page() {
return (
@@ -19,7 +21,6 @@ export default function Page() {
<br className="hidden sm:block" />
Built on MapLibre. Styled with Tailwind.
</PageHeaderDescription>
<PageActions>
<Button size="lg" asChild>
<Link href="/docs">Get Started</Link>
@@ -30,7 +31,14 @@ export default function Page() {
</PageActions>
</PageHeader>
<section className="container-wide">
<section
className="animate-fade-in animate-stagger container-wide pt-4"
style={
{
"--stagger": 4,
} as CSSProperties
}
>
<ExamplesGrid />
</section>

View File

@@ -1,7 +1,7 @@
"use client";
import { useState } from "react";
import { Check, Code, Eye, Fullscreen, Terminal } from "lucide-react";
import { Check, Fullscreen, Terminal } from "lucide-react";
import { Tabs, TabsContent, TabsList, TabsTrigger } from "@/components/ui/tabs";
import { Button } from "@/components/ui/button";
import { RegistryBlockItem, type FileTree } from "@/lib/blocks";
@@ -51,11 +51,9 @@ export function BlockPreview({
<div className="flex items-center justify-between">
<TabsList className="h-8!">
<TabsTrigger value="preview" className="text-xs">
<Eye className="size-3.5" />
Preview
</TabsTrigger>
<TabsTrigger value="code" className="text-xs">
<Code className="size-3.5" />
Code
</TabsTrigger>
</TabsList>
@@ -84,11 +82,11 @@ export function BlockPreview({
</div>
</div>
<TabsContent value="preview" className="mt-2">
<TabsContent value="preview" className="mt-3">
{children}
</TabsContent>
<TabsContent value="code" className="mt-2">
<TabsContent value="code" className="mt-3">
<BlockViewerCode tree={tree} highlightedFiles={highlightedFiles} />
</TabsContent>
</Tabs>

View File

@@ -36,7 +36,7 @@ interface BlockViewerCodeContext {
}
const BlockViewerCodeCtx = React.createContext<BlockViewerCodeContext | null>(
null
null,
);
function useBlockViewerCode() {
@@ -57,12 +57,12 @@ export function BlockViewerCode({
highlightedFiles,
}: BlockViewerCodeProps) {
const [activeFile, setActiveFile] = React.useState<string>(
highlightedFiles[0]?.target ?? ""
highlightedFiles[0]?.target ?? "",
);
const file = React.useMemo(
() => highlightedFiles.find((f) => f.target === activeFile),
[highlightedFiles, activeFile]
[highlightedFiles, activeFile],
);
if (!file) return null;
@@ -71,12 +71,12 @@ export function BlockViewerCode({
<BlockViewerCodeCtx.Provider
value={{ activeFile, setActiveFile, highlightedFiles, tree }}
>
<div className="flex overflow-hidden rounded-xl border h-(--block-preview-height)">
<div className="flex h-(--block-preview-height) overflow-hidden rounded-xl border">
<div className="w-64 shrink-0">
<FileTreeSidebar />
</div>
<div className="min-w-0 flex-1 flex flex-col">
<div className="flex h-12 shrink-0 items-center gap-2 border-b px-4 text-sm">
<div className="flex min-w-0 flex-1 flex-col">
<div className="bg-muted/40 flex h-12 shrink-0 items-center gap-2 border-b px-4 text-sm">
<span className="text-muted-foreground">{file.target}</span>
<div className="ml-auto">
<CopyCodeButton />
@@ -85,7 +85,7 @@ export function BlockViewerCode({
<div
key={file.path}
dangerouslySetInnerHTML={{ __html: file.highlightedContent }}
className="flex-1 overflow-y-auto p-4 text-sm [&_pre]:bg-transparent! [&_code]:bg-transparent!"
className="bg-muted/40 flex-1 overflow-y-auto p-4 text-sm [&_code]:bg-transparent! [&_pre]:bg-transparent!"
/>
</div>
</div>
@@ -98,7 +98,7 @@ function FileTreeSidebar() {
return (
<SidebarProvider className="flex min-h-full! flex-col border-r">
<Sidebar collapsible="none" className="w-full flex-1 bg-card">
<Sidebar collapsible="none" className="bg-card w-full flex-1">
<SidebarGroupLabel className="h-12 rounded-none border-b px-4 text-sm">
Files
</SidebarGroupLabel>
@@ -125,7 +125,7 @@ function TreeNode({ item, index }: { item: FileTree; index: number }) {
<SidebarMenuButton
isActive={item.path === activeFile}
onClick={() => item.path && setActiveFile(item.path)}
className="hover:bg-muted-foreground/15 focus:bg-muted-foreground/15 focus-visible:bg-muted-foreground/15 active:bg-muted-foreground/15 data-[active=true]:bg-muted-foreground/15 rounded-none whitespace-nowrap pl-(--index)"
className="hover:bg-muted-foreground/15 focus:bg-muted-foreground/15 focus-visible:bg-muted-foreground/15 active:bg-muted-foreground/15 data-[active=true]:bg-muted-foreground/15 rounded-none pl-(--index) whitespace-nowrap"
data-index={index}
style={
{
@@ -149,7 +149,7 @@ function TreeNode({ item, index }: { item: FileTree; index: number }) {
>
<CollapsibleTrigger asChild>
<SidebarMenuButton
className="hover:bg-muted-foreground/15 focus:bg-muted-foreground/15 focus-visible:bg-muted-foreground/15 active:bg-muted-foreground/15 data-[active=true]:bg-muted-foreground/15 rounded-none whitespace-nowrap pl-(--index)"
className="hover:bg-muted-foreground/15 focus:bg-muted-foreground/15 focus-visible:bg-muted-foreground/15 active:bg-muted-foreground/15 data-[active=true]:bg-muted-foreground/15 rounded-none pl-(--index) whitespace-nowrap"
style={
{
"--index": `${index * (index === 1 ? 1 : 1.2)}rem`,
@@ -179,7 +179,7 @@ function CopyCodeButton() {
const file = React.useMemo(
() => highlightedFiles.find((f) => f.target === activeFile),
[highlightedFiles, activeFile]
[highlightedFiles, activeFile],
);
if (!file) return null;
@@ -187,8 +187,7 @@ function CopyCodeButton() {
return (
<Button
variant="ghost"
size="icon"
className="size-7"
size="icon-sm"
onClick={async () => {
await navigator.clipboard.writeText(file.content);
setCopied(true);

View File

@@ -10,6 +10,7 @@ import { Footer } from "@/components/footer";
import { Metadata } from "next";
import { getAllBlocks } from "@/lib/blocks";
import { BlockDisplay } from "./_components/block-display";
import { type CSSProperties } from "react";
export const metadata: Metadata = {
title: "Map blocks for your application",
@@ -22,15 +23,15 @@ export default async function Page() {
return (
<>
<PageHeader align="left">
<PageHeaderHeading className="md:text-5xl">
<PageHeader align="left" showBackground={false}>
<PageHeaderHeading className="font-semibold md:text-5xl">
Map blocks for your application
</PageHeaderHeading>
<PageHeaderDescription className="md:text-lg">
Pre-built, ready-to-use map blocks. Browse, preview, and copy them
into your app with one command.
</PageHeaderDescription>
<PageActions className="delay-300">
<PageActions>
<Button asChild>
<a href="#blocks">Browse Blocks</a>
</Button>
@@ -41,8 +42,13 @@ export default async function Page() {
</PageHeader>
<section
className="animate-fade-up container scroll-mt-20 space-y-20 delay-400"
className="animate-fade-up animate-stagger container scroll-mt-20 space-y-20"
id="blocks"
style={
{
"--stagger": 4,
} as CSSProperties
}
>
{blocks.map((block) => (
<BlockDisplay key={block.name} name={block.name} />

View File

@@ -15,14 +15,14 @@ export async function CodeBlock({
const highlighted = await highlightCode(code, language);
return (
<div className="w-full rounded-lg border overflow-hidden">
<div className="relative w-full overflow-hidden rounded-lg border">
{showCopyButton && (
<div className="flex items-center justify-end border-b bg-muted/30 px-2 h-10">
<div className="absolute top-2 right-2 z-10">
<CopyButton text={code} />
</div>
)}
<div
className="p-4 overflow-auto text-sm bg-muted/20 [&_pre]:bg-transparent! [&_code]:bg-transparent!"
className="bg-muted/40 overflow-auto p-4 text-sm [&_code]:bg-transparent! [&_pre]:bg-transparent!"
dangerouslySetInnerHTML={{ __html: highlighted }}
/>
</div>

View File

@@ -2,6 +2,7 @@
import { useState } from "react";
import { cn } from "@/lib/utils";
import { Button } from "@/components/ui/button";
import { CopyButton } from "./copy-button";
interface ComponentPreviewClientProps {
@@ -17,48 +18,49 @@ export function ComponentPreviewClient({
highlightedCode,
className,
}: ComponentPreviewClientProps) {
const [activeTab, setActiveTab] = useState<"preview" | "code">("preview");
const [expanded, setExpanded] = useState(false);
return (
<div className="w-full rounded-lg border overflow-hidden">
<div className="flex items-center justify-between border-b bg-muted/30 px-2 h-12">
<div className="flex gap-2">
<button
onClick={() => setActiveTab("preview")}
className={cn(
"px-2 py-1 text-xs font-medium rounded transition-colors",
activeTab === "preview"
? "text-foreground bg-muted dark:bg-muted/80"
: "text-muted-foreground hover:text-foreground hover:bg-muted dark:hover:bg-muted/80"
)}
>
Preview
</button>
<button
onClick={() => setActiveTab("code")}
className={cn(
"px-3 py-1 text-xs font-medium rounded transition-colors",
activeTab === "code"
? "text-foreground bg-muted dark:bg-muted/80"
: "text-muted-foreground hover:text-foreground hover:bg-muted dark:hover:bg-muted/80"
)}
>
Code
</button>
</div>
<CopyButton text={code} />
<div className="space-y-4">
<div
className={cn(
"w-full overflow-hidden rounded-lg border",
"h-[420px]",
className,
)}
>
{children}
</div>
<div className={cn("h-[400px] overflow-hidden", className)}>
{activeTab === "preview" ? (
<div className="h-full">{children}</div>
) : (
<div
className="h-full p-4 overflow-auto text-sm bg-muted/20 [&_pre]:bg-transparent! [&_code]:bg-transparent!"
dangerouslySetInnerHTML={{ __html: highlightedCode }}
/>
)}
<div className="relative w-full overflow-hidden rounded-lg border">
<div className="absolute top-2 right-2 z-10">
<CopyButton text={code} />
</div>
<div
className={cn(
"bg-muted/40 overflow-hidden p-4 text-sm transition-[max-height] [&_code]:bg-transparent! [&_pre]:bg-transparent!",
expanded ? "max-h-[420px] overflow-auto" : "max-h-44",
)}
dangerouslySetInnerHTML={{ __html: highlightedCode }}
/>
<div
className={cn(
"absolute inset-x-0 bottom-0 flex w-full items-center justify-center",
!expanded &&
"from-background to-background/0 bg-linear-to-t pt-12 pb-6",
)}
>
{!expanded && (
<Button
variant="outline"
size="sm"
onClick={() => setExpanded(!expanded)}
className="bg-background hover:bg-muted dark:bg-background dark:hover:bg-muted"
>
View Code
</Button>
)}
</div>
</div>
</div>
);

View File

@@ -2,14 +2,13 @@
import { useState } from "react";
import { Check, Copy } from "lucide-react";
import { cn } from "@/lib/utils";
import { Button } from "@/components/ui/button";
interface CopyButtonProps {
text: string;
className?: string;
}
export function CopyButton({ text, className }: CopyButtonProps) {
export function CopyButton({ text }: CopyButtonProps) {
const [copied, setCopied] = useState(false);
const copy = async () => {
@@ -19,19 +18,14 @@ export function CopyButton({ text, className }: CopyButtonProps) {
};
return (
<button
<Button
variant="ghost"
size="icon-sm"
onClick={copy}
className={cn(
"p-1.5 rounded hover:bg-muted transition-colors",
className
)}
aria-label="Copy code"
className="text-muted-foreground"
>
{copied ? (
<Check className="size-3.5 text-emerald-500" />
) : (
<Copy className="size-3.5 text-muted-foreground" />
)}
</button>
{copied ? <Check /> : <Copy />}
</Button>
);
}

View File

@@ -22,15 +22,13 @@ export function DocsSidebar() {
return (
<Sidebar
className="sticky top-16 z-30 hidden h-[calc(100svh-4rem)] overscroll-none bg-transparent lg:flex"
className="sticky top-14 z-30 hidden h-[calc(100svh-3.5rem)] overscroll-none bg-transparent lg:flex"
collapsible="none"
>
<SidebarContent className="pt-6">
{docsNavigation.map((group) => (
<SidebarGroup key={group.title}>
<SidebarGroupLabel className="text-foreground">
{group.title}
</SidebarGroupLabel>
<SidebarGroupLabel>{group.title}</SidebarGroupLabel>
<SidebarGroupContent>
<SidebarMenu>
{group.items.map((item) => (
@@ -38,7 +36,7 @@ export function DocsSidebar() {
<SidebarMenuButton
asChild
isActive={pathname === item.href}
className="text-muted-foreground font-medium"
className="font-medium"
>
<Link
href={item.href}

View File

@@ -57,35 +57,30 @@ export function DocsToc({ items, className }: DocsTocProps) {
}
return (
<div className={cn("flex flex-col gap-1", className)}>
<p className="text-muted-foreground mb-2 flex items-center gap-1.5 text-xs font-medium">
<nav aria-label="On this page" className={cn("flex flex-col", className)}>
<p className="text-sidebar-foreground/70 mb-3 text-[13px] font-medium tracking-tight">
On This Page
</p>
<div className="relative">
<div className="bg-border absolute top-1 bottom-1 left-0 w-px" />
<div className="flex flex-col gap-1">
{items.map((item) => {
const isActive = item.slug === activeHeading;
return (
<a
key={item.slug}
href={`#${item.slug}`}
className={cn(
"relative py-1 pl-3 text-[0.8rem] no-underline transition-colors",
isActive
? "text-foreground"
: "text-muted-foreground hover:text-foreground",
)}
>
{isActive && (
<div className="bg-foreground absolute top-1 bottom-1 left-0 w-px rounded-full" />
)}
{item.title}
</a>
);
})}
</div>
<div className="flex flex-col gap-0.5">
{items.map((item) => {
const isActive = item.slug === activeHeading;
return (
<a
key={item.slug}
href={`#${item.slug}`}
className={cn(
"py-1 text-sm no-underline transition-colors",
isActive
? "text-foreground"
: "text-muted-foreground hover:text-foreground",
)}
>
{item.title}
</a>
);
})}
</div>
</div>
</nav>
);
}

View File

@@ -33,10 +33,10 @@ interface DocsTitleProps {
function DocsTitle({ title, description }: DocsTitleProps) {
return (
<div className="space-y-3">
<h1 className="text-3xl font-semibold tracking-tight text-foreground">
<h1 className="text-foreground text-3xl font-semibold tracking-tight">
{title}
</h1>
<p className="text-base text-muted-foreground leading-relaxed">
<p className="text-muted-foreground text-base leading-relaxed">
{description}
</p>
</div>
@@ -62,18 +62,18 @@ export function DocsLayout({
toc = [],
}: DocsLayoutProps) {
return (
<div className="flex">
<div className="flex-1 min-w-0 max-w-[800px] lg:px-4 mx-auto pb-20 pt-12">
<div className="flex size-full">
<div className="mx-auto flex max-w-[52rem] min-w-0 flex-1 flex-col pt-10 pb-20 lg:px-4">
<DocsTitle title={title} description={description} />
<div className="mt-12 space-y-12">{children}</div>
<div className="mt-12 mb-12 space-y-12">{children}</div>
{(prev || next) && (
<div className="flex items-center justify-between gap-4 mt-16">
<div className="mt-auto flex items-center justify-between gap-4">
{prev ? (
<Button
variant="ghost"
size="sm"
asChild
className="h-auto py-2 -ml-2"
className="-ml-2 h-auto py-2"
>
<Link href={prev.href}>
<ChevronLeft /> {prev.title}
@@ -87,7 +87,7 @@ export function DocsLayout({
variant="ghost"
size="sm"
asChild
className="h-auto py-2 -mr-2"
className="-mr-2 h-auto py-2"
>
<Link href={next.href}>
{next.title} <ChevronRight />
@@ -98,8 +98,8 @@ export function DocsLayout({
)}
</div>
<aside className="hidden xl:block w-44 shrink-0">
<nav className="sticky top-24">
<aside className="hidden w-42 shrink-0 xl:block">
<nav className="sticky top-24 max-h-[calc(100svh-7rem)] overflow-y-auto pr-1">
{toc.length > 0 && <DocsToc items={toc} />}
</nav>
</aside>
@@ -116,13 +116,13 @@ interface DocsSectionProps {
export function DocsSection({ title, children }: DocsSectionProps) {
const id = title ? slugify(title) : undefined;
return (
<section className="space-y-5 scroll-m-24" id={id}>
<section className="scroll-m-24 space-y-5" id={id}>
{title && (
<h2 className="text-xl font-semibold tracking-tight text-foreground">
<h2 className="text-foreground text-xl font-semibold tracking-tight">
{title}
</h2>
)}
<div className="text-base text-foreground/80 leading-7 space-y-4 [&_p]:leading-7 [&_ul]:list-disc [&_ul]:pl-5 [&_ul]:space-y-2 [&_ol]:list-decimal [&_ol]:pl-5 [&_ol]:space-y-2 [&_li]:leading-7 [&_strong]:text-foreground [&_strong]:font-medium [&_em]:text-muted-foreground">
<div className="text-primary [&_strong]:text-foreground [&_em]:text-muted-foreground space-y-4 text-base leading-7 [&_li]:leading-7 [&_ol]:list-decimal [&_ol]:space-y-2 [&_ol]:pl-5 [&_p]:leading-7 [&_strong]:font-medium [&_ul]:list-disc [&_ul]:space-y-2 [&_ul]:pl-5">
{children}
</div>
</section>
@@ -136,7 +136,7 @@ interface DocsNoteProps {
export function DocsNote({ children }: DocsNoteProps) {
return (
<div className="rounded-lg border bg-muted/30 px-5 py-4 text-[14px] leading-relaxed text-foreground/70 [&_strong]:text-foreground [&_strong]:font-medium">
<div className="bg-muted/40 text-foreground/80 [&_strong]:text-foreground rounded-lg border px-5 py-4 text-[15px] leading-relaxed [&_strong]:font-medium">
{children}
</div>
);
@@ -155,7 +155,7 @@ export function DocsLink({ href, children, external }: DocsLinkProps) {
href={href}
target={external ? "_blank" : undefined}
rel={external ? "noopener noreferrer" : undefined}
className="font-medium text-foreground underline underline-offset-4 transition-colors"
className="text-foreground font-medium underline underline-offset-4"
>
{children}
</Link>
@@ -173,8 +173,8 @@ export function DocsCode({
return (
<code
className={cn(
"relative rounded bg-muted px-[0.3rem] py-[0.2rem] font-mono text-sm",
className
"bg-muted relative rounded-md px-2 py-1 font-mono text-sm",
className,
)}
>
{children}
@@ -194,10 +194,10 @@ interface DocsPropTableProps {
export function DocsPropTable({ props }: DocsPropTableProps) {
return (
<div className="rounded-lg border overflow-hidden my-6">
<div className="my-6 overflow-hidden rounded-lg border">
<Table>
<TableHeader>
<TableRow className="hover:bg-transparent bg-muted/30">
<TableRow className="bg-muted/30 hover:bg-transparent">
<TableHead className="h-10 px-4 text-xs font-medium">
Prop
</TableHead>
@@ -219,16 +219,16 @@ export function DocsPropTable({ props }: DocsPropTableProps) {
<DocsCode className="text-[13px]">{prop.name}</DocsCode>
</TableCell>
<TableCell className="px-4 py-3 align-top whitespace-normal">
<DocsCode className="text-xs text-muted-foreground">
<DocsCode className="text-muted-foreground text-xs">
{prop.type}
</DocsCode>
</TableCell>
<TableCell className="px-4 py-3 align-top">
<DocsCode className="text-xs text-muted-foreground whitespace-normal">
<DocsCode className="text-muted-foreground text-xs whitespace-normal">
{prop.default ?? "—"}
</DocsCode>
</TableCell>
<TableCell className="px-4 py-3 text-sm text-foreground/70 whitespace-normal min-w-[180px] leading-relaxed">
<TableCell className="text-foreground/70 min-w-[180px] px-4 py-3 text-sm leading-relaxed whitespace-normal">
{prop.description}
</TableCell>
</TableRow>

View File

@@ -46,15 +46,15 @@ function MapController() {
<div className="absolute top-3 left-3 z-10 flex flex-col gap-2">
<div className="flex gap-2">
<Button size="sm" variant="secondary" onClick={handle3DView}>
<Mountain className="size-4 mr-1.5" />
<Mountain className="mr-1.5 size-4" />
3D View
</Button>
<Button size="sm" variant="secondary" onClick={handleReset}>
<RotateCcw className="size-4 mr-1.5" />
<RotateCcw className="mr-1.5 size-4" />
Reset
</Button>
</div>
<div className="rounded-md bg-background/90 backdrop-blur px-3 py-2 text-xs font-mono border">
<div className="bg-background/90 rounded-md border px-3 py-2 font-mono text-xs backdrop-blur">
<div>Pitch: {pitch}°</div>
<div>Bearing: {bearing}°</div>
</div>
@@ -64,7 +64,7 @@ function MapController() {
export function AdvancedUsageExample() {
return (
<div className="h-[400px] w-full">
<div className="h-[420px] w-full">
<Map center={[-73.9857, 40.7484]} zoom={15}>
<MapController />
</Map>

View File

@@ -2,7 +2,7 @@ import { Map } from "@/registry/map";
export function BasicMapExample() {
return (
<div className="h-[400px] w-full">
<div className="h-[420px] w-full">
<Map center={[-74.006, 40.7128]} zoom={12} />
</div>
);

View File

@@ -16,7 +16,7 @@ export default function ClusterExample() {
} | null>(null);
return (
<div className="h-[400px] w-full">
<div className="h-[420px] w-full">
<Map center={[-103.59, 40.66]} zoom={3.4} fadeDuration={0}>
<MapClusterLayer<EarthquakeProperties>
data="https://maplibre.org/maplibre-gl-js/docs/assets/earthquakes.geojson"

View File

@@ -12,9 +12,9 @@ export function ControlledMapExample() {
});
return (
<div className="h-[400px] relative w-full">
<div className="relative h-[420px] w-full">
<Map viewport={viewport} onViewportChange={setViewport} />
<div className="absolute top-2 left-2 z-10 flex flex-wrap gap-x-3 gap-y-1 text-xs font-mono bg-background/80 backdrop-blur px-2 py-1.5 rounded border">
<div className="bg-background/80 absolute top-2 left-2 z-10 flex flex-wrap gap-x-3 gap-y-1 rounded border px-2 py-1.5 font-mono text-xs backdrop-blur">
<span>
<span className="text-muted-foreground">lng:</span>{" "}
{viewport.center[0].toFixed(3)}

View File

@@ -145,16 +145,16 @@ function CustomLayer() {
onClick={toggleLayer}
>
{isLayerVisible ? (
<X className="size-4 mr-1.5" />
<X className="mr-1.5 size-4" />
) : (
<Layers className="size-4 mr-1.5" />
<Layers className="mr-1.5 size-4" />
)}
{isLayerVisible ? "Hide Parks" : "Show Parks"}
</Button>
</div>
{hoveredPark && (
<div className="absolute bottom-3 left-3 z-10 rounded-md bg-background/90 backdrop-blur px-3 py-2 text-sm font-medium border">
<div className="bg-background/90 absolute bottom-3 left-3 z-10 rounded-md border px-3 py-2 text-sm font-medium backdrop-blur">
{hoveredPark}
</div>
)}
@@ -164,7 +164,7 @@ function CustomLayer() {
export function CustomLayerExample() {
return (
<div className="h-[400px] w-full">
<div className="h-[420px] w-full">
<Map center={[-73.97, 40.78]} zoom={11.8}>
<MapControls />
<CustomLayer />

View File

@@ -22,7 +22,7 @@ export function CustomStyleExample() {
}, [is3D]);
return (
<div className="h-[400px] relative w-full">
<div className="relative h-[420px] w-full">
<Map
ref={mapRef}
center={[-0.1276, 51.5074]}
@@ -37,7 +37,7 @@ export function CustomStyleExample() {
<select
value={style}
onChange={(e) => setStyle(e.target.value as StyleKey)}
className="bg-background text-foreground border rounded-md px-2 py-1 text-sm shadow"
className="bg-background text-foreground rounded-md border px-2 py-1 text-sm shadow"
>
<option value="default">Default (Carto)</option>
<option value="openstreetmap">OpenStreetMap</option>

View File

@@ -11,7 +11,7 @@ export function DraggableMarkerExample() {
});
return (
<div className="h-[400px] w-full">
<div className="h-[420px] w-full">
<Map center={[-73.98, 40.75]} zoom={12}>
<MapMarker
draggable
@@ -31,8 +31,8 @@ export function DraggableMarkerExample() {
</MarkerContent>
<MarkerPopup>
<div className="space-y-1">
<p className="font-medium text-foreground">Coordinates</p>
<p className="text-xs text-muted-foreground">
<p className="text-foreground font-medium">Coordinates</p>
<p className="text-muted-foreground text-xs">
{draggableMarker.lat.toFixed(4)},{" "}
{draggableMarker.lng.toFixed(4)}
</p>

View File

@@ -49,7 +49,7 @@ function MarkersLayer() {
const sourceId = `markers-source-${id}`;
const layerId = `markers-layer-${id}`;
const [selectedPoint, setSelectedPoint] = useState<SelectedPoint | null>(
null
null,
);
useEffect(() => {
@@ -76,14 +76,14 @@ function MarkersLayer() {
const handleClick = (
e: maplibregl.MapMouseEvent & {
features?: maplibregl.MapGeoJSONFeature[];
}
},
) => {
if (!e.features?.length) return;
const feature = e.features[0];
const coords = (feature.geometry as GeoJSON.Point).coordinates as [
number,
number
number,
];
setSelectedPoint({
@@ -134,7 +134,7 @@ function MarkersLayer() {
>
<div className="min-w-[140px]">
<p className="font-medium">{selectedPoint.name}</p>
<p className="text-sm text-muted-foreground">
<p className="text-muted-foreground text-sm">
{selectedPoint.category}
</p>
</div>
@@ -146,7 +146,7 @@ function MarkersLayer() {
export function LayerMarkersExample() {
return (
<div className="h-[400px] w-full">
<div className="h-[420px] w-full">
<Map center={[-73.98, 40.75]} zoom={11}>
<MarkersLayer />
</Map>

View File

@@ -2,7 +2,7 @@ import { Map, MapControls } from "@/registry/map";
export function MapControlsExample() {
return (
<div className="h-[400px] w-full">
<div className="h-[420px] w-full">
<Map center={[2.3522, 48.8566]} zoom={11}>
<MapControls
position="bottom-right"

View File

@@ -24,7 +24,7 @@ const locations = [
export function MarkersExample() {
return (
<div className="h-[400px] w-full">
<div className="h-[420px] w-full">
<Map center={[-73.98, 40.76]} zoom={12}>
{locations.map((location) => (
<MapMarker
@@ -33,13 +33,13 @@ export function MarkersExample() {
latitude={location.lat}
>
<MarkerContent>
<div className="size-4 rounded-full bg-primary border-2 border-white shadow-lg" />
<div className="bg-primary size-4 rounded-full border-2 border-white shadow-lg" />
</MarkerContent>
<MarkerTooltip>{location.name}</MarkerTooltip>
<MarkerPopup>
<div className="space-y-1">
<p className="font-medium text-foreground">{location.name}</p>
<p className="text-xs text-muted-foreground">
<p className="text-foreground font-medium">{location.name}</p>
<p className="text-muted-foreground text-xs">
{location.lat.toFixed(4)}, {location.lng.toFixed(4)}
</p>
</div>

View File

@@ -22,14 +22,14 @@ const stops = [
export function RouteExample() {
return (
<div className="h-[400px] w-full">
<div className="h-[420px] w-full">
<Map center={[-73.98, 40.75]} zoom={11.2}>
<MapRoute coordinates={route} color="#3b82f6" width={4} opacity={0.8} />
{stops.map((stop, index) => (
<MapMarker key={stop.name} longitude={stop.lng} latitude={stop.lat}>
<MarkerContent>
<div className="size-4.5 rounded-full bg-blue-500 border-2 border-white shadow-lg flex items-center justify-center text-white text-xs font-semibold">
<div className="flex size-4.5 items-center justify-center rounded-full border-2 border-white bg-blue-500 text-xs font-semibold text-white shadow-lg">
{index + 1}
</div>
</MarkerContent>

View File

@@ -8,7 +8,7 @@ export function StandalonePopupExample() {
const [showPopup, setShowPopup] = useState(true);
return (
<div className="h-[400px] w-full relative">
<div className="relative h-[420px] w-full">
<Map center={[-74.006, 40.7128]} zoom={13}>
{showPopup && (
<MapPopup
@@ -21,8 +21,8 @@ export function StandalonePopupExample() {
className="w-62"
>
<div className="space-y-2">
<h3 className="font-semibold text-foreground">New York City</h3>
<p className="text-sm text-muted-foreground">
<h3 className="text-foreground font-semibold">New York City</h3>
<p className="text-muted-foreground text-sm">
The city that never sleeps. Population: 8.3 million
</p>
<Button

View File

@@ -68,7 +68,7 @@ export default function InstallationPage() {
<DocsSection title="Usage">
<p>Import and use the map component:</p>
<CodeBlock code={usageCode} />
<Card className="h-[320px] p-0 overflow-hidden rounded-lg">
<Card className="h-[320px] overflow-hidden rounded-lg p-0">
<Map center={[-74.006, 40.7128]} zoom={11}>
<MapControls />
</Map>
@@ -77,8 +77,7 @@ export default function InstallationPage() {
<DocsNote>
<strong>Note:</strong> The map uses free CARTO basemap tiles by default.
No API key required. Tiles automatically switch between light and dark
themes.
Tiles automatically switch between light and dark themes.
</DocsNote>
</DocsLayout>
);

View File

@@ -7,8 +7,8 @@ export default function DocsLayout({
children: React.ReactNode;
}) {
return (
<div className="container flex flex-col flex-1">
<SidebarProvider>
<div className="flex flex-1">
<SidebarProvider className="container min-h-min px-0">
<DocsSidebar />
<main className="size-full">{children}</main>
</SidebarProvider>

View File

@@ -45,11 +45,11 @@ export default function IntroductionPage() {
return (
<DocsLayout
title="Introduction"
description="Beautiful, accessible map components."
description="Copy-paste map components for React."
next={{ title: "Installation", href: "/docs/installation" }}
toc={[
{ title: "Philosophy", slug: "philosophy" },
{ title: "Why mapcn?", slug: "why-mapcn" },
{ title: "Why MapLibre Directly?", slug: "why-maplibre-directly" },
{ title: "Any Map Style", slug: "any-map-style" },
{ title: "Features", slug: "features" },
]}
@@ -73,58 +73,51 @@ export default function IntroductionPage() {
</p>
</DocsSection>
<DocsSection title="Philosophy">
<p>
mapcn follows the shadcn model for maps: copy-paste components you can
own, with zero lock-in and sensible defaults that work immediately.
</p>
<p>
Maps are often treated as black boxes hidden behind wrapper libraries
and configuration-heavy SDKs. mapcn takes a different approach. It
stays close to MapLibre, keeps the API familiar, and lets you drop
down to the raw map instance whenever you need more control.
</p>
<p>
The goal is simple: make maps feel like the rest of your UI stack -
composable, themeable, accessible, and easy to customize with Tailwind
and shadcn patterns.
</p>
</DocsSection>
<DocsSection title="Why mapcn?">
<p>
There&apos;s no proper copy-paste, easy-to-use map integration for
React. Most solutions require complex configurations, API keys, or
heavy wrapper libraries. mapcn solves this:
Most React map setups are either too opinionated or too heavy. mapcn
is built for teams that want to ship quickly without giving up
control:
</p>
<ul>
<li>
<strong className="text-foreground">One Command:</strong> Run the
install, get a working map. No config files, no API keys, no setup.
</li>
<li>
<strong className="text-foreground">Own Your Code:</strong> Copy the
components into your project. Modify anything.
components into your project and customize everything.
</li>
<li>
<strong className="text-foreground">No Wrapper Overhead:</strong>{" "}
Built directly on MapLibre. Drop to the raw API whenever you need.
<strong className="text-foreground">Start Fast:</strong> Run one
command and render your first map with production-ready defaults.
</li>
<li>
<strong className="text-foreground">Looks Good Already:</strong>{" "}
Thoughtful defaults with dark mode. Style with Tailwind as needed.
<strong className="text-foreground">Scale Safely:</strong> Build on
top of MapLibre directly, then drop to raw APIs when needed.
</li>
<li>
<strong className="text-foreground">Works Anywhere:</strong> Bring
your own tiles MapTiler, Carto, OSM, or any MapLibre-compatible
source.
<strong className="text-foreground">Design-System Friendly:</strong>{" "}
Styled with Tailwind and made to fit naturally with shadcn/ui
patterns.
</li>
</ul>
</DocsSection>
<DocsSection title="Why MapLibre Directly?">
<p>
mapcn uses{" "}
<DocsLink href="https://maplibre.org" external>
MapLibre
</DocsLink>{" "}
directly instead of wrapper libraries like{" "}
<DocsLink href="https://visgl.github.io/react-map-gl" external>
react-map-gl
</DocsLink>
. This keeps components close to the underlying API when you copy a
mapcn component, you fully own the map instance without extra
framework dependencies.
</p>
<p>
UI elements like markers, popups, and tooltips are rendered via React
portals, giving you complete styling freedom. You can drop down to raw
MapLibre APIs anytime without &quot;escaping&quot; a wrapper.
</p>
</DocsSection>
<DocsSection title="Any Map Style">
<p>
mapcn works with any{" "}
@@ -135,10 +128,10 @@ export default function IntroductionPage() {
</p>
<ul>
<li>
<DocsLink href="https://www.maptiler.com" external>
MapTiler
<DocsLink href="https://www.openstreetmap.org" external>
OpenStreetMap
</DocsLink>{" "}
- Beautiful vector tiles with extensive customization options
- Community-driven, open-source map data
</li>
<li>
<DocsLink href="https://carto.com/basemaps" external>
@@ -147,10 +140,10 @@ export default function IntroductionPage() {
- Clean, minimal basemaps perfect for data visualization
</li>
<li>
<DocsLink href="https://www.openstreetmap.org" external>
OpenStreetMap
<DocsLink href="https://www.maptiler.com" external>
MapTiler
</DocsLink>{" "}
- Community-driven, open-source map data
- Beautiful vector tiles with extensive customization options
</li>
<li>
<DocsLink href="https://stadiamaps.com" external>
@@ -169,19 +162,19 @@ export default function IntroductionPage() {
</DocsSection>
<DocsSection title="Features">
<div className="grid gap-4 sm:grid-cols-2 mt-4 not-prose">
<div className="not-prose mt-4 grid gap-4 sm:grid-cols-2">
{features.map((feature) => (
<div
key={feature.title}
className="rounded-lg border bg-card p-4 space-y-2"
className="bg-card space-y-2 rounded-lg border p-4"
>
<div className="flex items-center gap-2">
<div className="flex size-8 items-center justify-center rounded-md bg-primary/10">
<feature.icon className="size-4 text-primary" />
<div className="bg-primary/10 flex size-8 items-center justify-center rounded-md">
<feature.icon className="text-primary size-4" />
</div>
<h3 className="font-medium text-foreground">{feature.title}</h3>
<h3 className="text-foreground font-medium">{feature.title}</h3>
</div>
<p className="text-sm text-muted-foreground">
<p className="text-muted-foreground text-sm">
{feature.description}
</p>
</div>

View File

@@ -6,6 +6,10 @@ interface BlockViewPageProps {
params: Promise<{ name: string }>;
}
export function generateStaticParams() {
return Object.keys(blockComponents).map((name) => ({ name }));
}
export const generateMetadata = async ({ params }: BlockViewPageProps) => {
const { name } = await params;
const Component = blockComponents[name];

View File

@@ -18,7 +18,7 @@ import {
CommandItem,
CommandList,
} from "@/components/ui/command";
import { Kbd, KbdGroup } from "@/components/ui/kbd";
import { Kbd } from "@/components/ui/kbd";
import { siteNavigation } from "@/lib/site-navigation";
import { Button } from "./ui/button";
import { cn } from "@/lib/utils";
@@ -52,16 +52,13 @@ export function CommandSearch({ className }: { className?: string }) {
onClick={() => setOpen(true)}
aria-label="Jump to pages, components, and docs"
className={cn(
"border-border/50 bg-muted/40 text-muted-foreground hover:bg-muted/70 hover:text-foreground hidden h-8 w-48 items-center gap-2 rounded-md border px-2.5 md:flex",
"bg-muted/40 text-muted-foreground hover:bg-muted/60 dark:hover:bg-muted/60 hover:text-foreground mr-2.5 hidden w-48 border md:flex",
className,
)}
>
<SearchIcon className="size-3.5" />
<span>Search...</span>
<KbdGroup className="ml-auto">
<Kbd></Kbd>
<Kbd>K</Kbd>
</KbdGroup>
<Kbd className="ml-auto bg-transparent">K</Kbd>
</Button>
<CommandDialog
open={open}
@@ -78,7 +75,7 @@ export function CommandSearch({ className }: { className?: string }) {
<CommandEmpty className="text-muted-foreground py-8 text-sm">
<div className="flex flex-col items-center gap-1.5">
<FileText className="size-5 opacity-40" />
<span>No results found.</span>
<span>No results found</span>
</div>
</CommandEmpty>
{siteNavigation.map((group) => (
@@ -88,7 +85,6 @@ export function CommandSearch({ className }: { className?: string }) {
key={item.href}
value={item.title}
onSelect={() => handleSelect(item.href)}
className="p-0"
>
<item.icon />
<span>{item.title}</span>

View File

@@ -37,7 +37,7 @@ export function Footer() {
<div className="grid grid-cols-2 gap-8 md:grid-cols-4">
<div className="col-span-2 md:col-span-1">
<Logo className="w-fit" />
<p className="text-muted-foreground mt-3 max-w-xs text-sm leading-relaxed">
<p className="text-muted-foreground mt-2 max-w-xs text-sm leading-relaxed">
Free & open-source, ready-to-use, customizable map components for
React.
</p>

View File

@@ -21,7 +21,7 @@ export function GitHubButton({ withCount = true }: { withCount?: boolean }) {
/>
</svg>
{withCount && (
<Suspense fallback={<Skeleton className="w-6 h-4 rounded" />}>
<Suspense fallback={<Skeleton className="h-4 w-6 rounded" />}>
<StarCount />
</Suspense>
)}
@@ -33,7 +33,7 @@ export function GitHubButton({ withCount = true }: { withCount?: boolean }) {
export async function StarCount() {
const response = await fetch(
"https://api.github.com/repos/AnmolSaini16/mapcn",
{ next: { revalidate: 60 } }
{ next: { revalidate: 60 } },
);
const data = await response.json();
@@ -46,7 +46,7 @@ export async function StarCount() {
return (
<>
{formattedCount && (
<span className="text-xs text-muted-foreground tabular-nums pt-0.5">
<span className="text-muted-foreground pt-0.5 text-xs tabular-nums">
{formattedCount}
</span>
)}

View File

@@ -1,53 +1,32 @@
import { Heart } from "lucide-react";
import { Logo } from "@/components/logo";
import { MainNav } from "@/components/main-nav";
import { MobileNav } from "@/components/mobile-nav";
import { Button } from "@/components/ui/button";
import { Separator } from "@/components/ui/separator";
import {
Tooltip,
TooltipContent,
TooltipTrigger,
} from "@/components/ui/tooltip";
import { GitHubButton } from "@/components/github-button";
import { ThemeToggle } from "@/components/theme-toggle";
import { CommandSearch } from "@/components/command-search";
import { cn } from "@/lib/utils";
import { Separator } from "./ui/separator";
export function Header({ className }: { className?: string }) {
return (
<header
className={cn("bg-background sticky top-0 z-50 h-14 w-full", className)}
className={cn(
"bg-background/85 supports-backdrop-filter:bg-background/70 sticky top-0 z-50 h-14 w-full backdrop-blur",
className,
)}
>
<nav className="container flex size-full items-center">
<nav className="container flex size-full items-center gap-2">
<MobileNav />
<Logo className="mr-3 hidden shrink-0 lg:flex" />
<Logo className="hidden shrink-0 lg:flex" />
<Separator
className="ml-2.5 hidden h-4! w-px lg:block"
orientation="vertical"
/>
<MainNav className="hidden lg:flex" />
<div className="ml-auto flex h-4.5 items-center gap-2">
<div className="ml-auto flex items-center gap-1.5">
<CommandSearch />
<Separator orientation="vertical" className="ml-2 hidden md:block" />
<Tooltip>
<TooltipTrigger asChild>
<Button variant="ghost" size="sm" asChild>
<a
href="https://github.com/sponsors/AnmolSaini16"
target="_blank"
rel="noopener noreferrer"
>
<Heart className="size-3.5 text-pink-500" />
Sponsor
</a>
</Button>
</TooltipTrigger>
<TooltipContent>
<p>Sponsor this project</p>
</TooltipContent>
</Tooltip>
<Separator orientation="vertical" />
<GitHubButton />
<Separator orientation="vertical" />
<ThemeToggle />
</div>
</nav>

View File

@@ -9,25 +9,20 @@ interface LogoProps {
}
export function Logo({ className, onClick, isLink = true }: LogoProps) {
const logoClasses =
"inline-flex items-center gap-1.5 text-base leading-none font-bold";
return isLink ? (
<Link
href="/"
onClick={onClick}
className={cn(
"flex h-8 items-center gap-1.5 text-lg font-semibold",
className,
)}
className={cn(logoClasses, "h-8", className)}
>
<MapPin className="size-4" />
mapcn
</Link>
) : (
<div
className={cn(
"flex items-center gap-1.5 text-lg font-semibold",
className,
)}
>
<div className={cn(logoClasses, className)}>
<MapPin className="size-4" />
mapcn
</div>

View File

@@ -12,13 +12,16 @@ export function MainNav({ className, ...props }: React.ComponentProps<"nav">) {
if (!navItems?.length) return null;
return (
<nav className={cn("flex items-center", className)} {...props}>
<nav className={cn("flex items-center gap-0.5", className)} {...props}>
{navItems.map((item) => (
<Button key={item.href} variant="ghost" asChild size="sm">
<Link
href={item.href}
className="relative inline-flex items-center gap-1.5"
>
<Button
key={item.href}
variant="ghost"
asChild
size="sm"
className="text-primary"
>
<Link href={item.href}>
<span>{item.title}</span>
</Link>
</Button>

View File

@@ -1,7 +1,7 @@
"use client";
import { cn } from "@/lib/utils";
import { createContext, useContext } from "react";
import { createContext, useContext, type CSSProperties } from "react";
type HeaderAlign = "center" | "left";
@@ -34,12 +34,13 @@ function PageHeader({
{showBackground && (
<div className="pointer-events-none absolute inset-x-0 -inset-y-10 overflow-hidden">
<div
className="absolute inset-0 opacity-[0.20] dark:opacity-[0.12]"
className="absolute inset-0 opacity-[0.16] dark:opacity-[0.12]"
style={{
backgroundImage: `radial-gradient(circle, currentColor 1px, transparent 1px)`,
backgroundSize: "24px 24px",
}}
/>
<div className="from-background to-background absolute inset-0 bg-linear-to-b via-transparent" />
</div>
)}
@@ -76,10 +77,15 @@ function PageHeaderHeading({
return (
<Comp
className={cn(
"animate-fade-up max-w-4xl text-4xl font-semibold tracking-tight delay-100 sm:text-5xl md:text-6xl",
"animate-fade-up animate-stagger max-w-4xl text-4xl font-bold tracking-tight sm:text-5xl md:text-6xl",
align === "center" ? "text-center" : "text-left",
className,
)}
style={
{
"--stagger": 1,
} as CSSProperties
}
>
<span className="from-foreground via-foreground to-foreground/65 bg-linear-to-b bg-clip-text text-transparent">
{children}
@@ -102,32 +108,25 @@ function PageHeaderDescription({
return (
<p
className={cn(
"text-muted-foreground animate-fade-up max-w-2xl leading-relaxed delay-200 sm:text-lg sm:leading-relaxed md:text-xl md:leading-relaxed",
"text-muted-foreground animate-fade-up animate-stagger max-w-2xl leading-relaxed sm:text-lg sm:leading-relaxed md:text-xl md:leading-relaxed",
align === "center" ? "text-center" : "text-left",
className,
)}
style={
{
"--stagger": 2,
} as CSSProperties
}
>
{children}
</p>
);
}
interface PageContentProps {
children: React.ReactNode;
className?: string;
}
function PageContent({ children, className }: PageContentProps) {
return (
<div className={cn("animate-fade-up w-full max-w-lg delay-300", className)}>
{children}
</div>
);
}
interface PageActionsProps {
children: React.ReactNode;
className?: string;
stagger?: number;
}
function PageActions({ children, className }: PageActionsProps) {
@@ -136,20 +135,19 @@ function PageActions({ children, className }: PageActionsProps) {
return (
<div
className={cn(
"animate-fade-up mt-3 flex flex-wrap items-center gap-3 delay-400",
"animate-fade-up animate-stagger mt-3 flex flex-wrap items-center gap-3",
align === "center" ? "justify-center" : "justify-start",
className,
)}
style={
{
"--stagger": 3,
} as CSSProperties
}
>
{children}
</div>
);
}
export {
PageHeader,
PageHeaderHeading,
PageHeaderDescription,
PageContent,
PageActions,
};
export { PageHeader, PageHeaderHeading, PageHeaderDescription, PageActions };

View File

@@ -50,7 +50,7 @@ function CommandDialog({
</DialogHeader>
<DialogContent
className={cn(
"top-[min(18vh,6.5rem)] translate-y-0 overflow-hidden p-0 sm:max-w-lg",
"top-[min(22vh,7.5rem)] translate-y-0 overflow-hidden p-0 sm:max-w-lg",
className,
)}
showCloseButton={showCloseButton}

View File

@@ -71,7 +71,7 @@ export default function Page() {
setLoading(true);
try {
const response = await fetch(
`https://router.project-osrm.org/route/v1/driving/${pickup.lng},${pickup.lat};${dropoff.lng},${dropoff.lat}?overview=full&geometries=geojson`
`https://router.project-osrm.org/route/v1/driving/${pickup.lng},${pickup.lat};${dropoff.lng},${dropoff.lat}?overview=full&geometries=geojson`,
);
const data = await response.json();
const route = data?.routes?.[0];
@@ -96,8 +96,8 @@ export default function Page() {
const progressCount = Math.max(
2,
Math.floor(
(routeData?.coordinates?.length ?? 0) * (routeData ? 0.62 : 0.66)
)
(routeData?.coordinates?.length ?? 0) * (routeData ? 0.62 : 0.66),
),
);
return routeData?.coordinates?.slice(0, progressCount) ?? [];
}, [routeData]);
@@ -106,13 +106,13 @@ export default function Page() {
return (
<div className="p-8">
<div className="grid md:grid-cols-[1.05fr_1fr] bg-sidebar rounded-lg border md:h-[600px] max-w-7xl mx-auto">
<div className="p-5 md:p-6 flex flex-col">
<div className="bg-sidebar mx-auto grid max-w-7xl rounded-lg border md:h-[600px] md:grid-cols-[1.05fr_1fr]">
<div className="flex flex-col p-5 md:p-6">
<div className="space-y-1">
<h3 className="text-2xl font-semibold tracking-tight">
Track Delivery
</h3>
<p className="text-sm text-muted-foreground">Mon Feb 10 - 2-3 PM</p>
<p className="text-muted-foreground text-sm">Mon Feb 10 - 2-3 PM</p>
</div>
<Card className="mt-5">
@@ -124,14 +124,14 @@ export default function Page() {
<CardContent className="space-y-5">
{deliveryMeals.map((meal) => (
<div key={meal.name} className="flex items-center gap-3">
<div className="grid size-8 place-items-center rounded-full bg-muted text-xs font-medium">
<Utensils className="size-4 text-muted-foreground" />
<div className="bg-muted grid size-8 place-items-center rounded-full text-xs font-medium">
<Utensils className="text-muted-foreground size-4" />
</div>
<div className="min-w-4 flex-1">
<p className="truncate text-sm font-medium pb-1">
<p className="truncate pb-1 text-sm font-medium">
{meal.name}
</p>
<p className="text-xs text-muted-foreground">
<p className="text-muted-foreground text-xs">
{meal.price}
</p>
</div>
@@ -143,7 +143,7 @@ export default function Page() {
</Badge>
</div>
))}
<div className="flex items-center justify-between border-t border-border/60 pt-3 text-sm">
<div className="border-border/60 flex items-center justify-between border-t pt-3 text-sm">
<span className="text-muted-foreground">Bundle total</span>
<span className="font-medium">$189.00</span>
</div>
@@ -153,7 +153,7 @@ export default function Page() {
<div className="mt-4 grid gap-3 sm:grid-cols-2">
<Card>
<CardContent className="space-y-2">
<p className="text-sm font-medium text-muted-foreground">
<p className="text-muted-foreground text-sm font-medium">
Pickup confirmed
</p>
<p className="text-sm font-medium">Mon, Feb 10 at 1:48 PM</p>
@@ -161,7 +161,7 @@ export default function Page() {
</Card>
<Card>
<CardContent className="space-y-2">
<p className="text-sm font-medium text-muted-foreground">
<p className="text-muted-foreground text-sm font-medium">
Remaining travel
</p>
<p className="text-sm font-medium">
@@ -183,7 +183,7 @@ export default function Page() {
</div>
</div>
<div className="relative h-[400px] overflow-hidden rounded-xl md:h-full shadow-sm">
<div className="relative h-[400px] overflow-hidden rounded-xl shadow-sm md:h-full">
<Map
loading={loading}
center={[-122.435, 37.696]}
@@ -228,7 +228,7 @@ export default function Page() {
<p className="font-medium">
Order {formatDuration(routeData?.duration)} away
</p>
<p className="text-muted-foreground">
<p className="text-background/70">
Route {formatDistance(routeData?.distance)}
</p>
</div>
@@ -240,18 +240,14 @@ export default function Page() {
<MarkerContent>
<div className="size-4 rounded-full border-2 border-white bg-emerald-500 shadow-sm" />
</MarkerContent>
<MarkerTooltip>
<p className="text-xs font-medium">Origin</p>
</MarkerTooltip>
<MarkerTooltip>Origin</MarkerTooltip>
</MapMarker>
<MapMarker longitude={dropoff.lng} latitude={dropoff.lat}>
<MarkerContent>
<div className="size-4 rounded-full border-2 border-white bg-rose-500 shadow-sm" />
</MarkerContent>
<MarkerTooltip>
<p className="text-xs font-medium">Destination</p>
</MarkerTooltip>
<MarkerTooltip>Destination</MarkerTooltip>
</MapMarker>
</Map>
</div>

View File

@@ -149,7 +149,6 @@
@apply max-w-[1640px] mx-auto px-6;
}
/* animations for the home page */
@keyframes fade-up {
from {
opacity: 0;
@@ -193,15 +192,11 @@
animation: scale-in 0.5s ease-out both;
}
.delay-100 { animation-delay: 100ms; }
.delay-200 { animation-delay: 200ms; }
.delay-300 { animation-delay: 300ms; }
.delay-400 { animation-delay: 400ms; }
.delay-500 { animation-delay: 500ms; }
.delay-600 { animation-delay: 600ms; }
.delay-700 { animation-delay: 700ms; }
.delay-800 { animation-delay: 800ms; }
.delay-900 { animation-delay: 900ms; }
.animate-stagger {
--stagger: 0;
--delay: 100ms;
animation-delay: calc(var(--stagger) * var(--delay));
}
/* Shiki dual theme support for code blocks */
html .shiki,