mirror of
https://github.com/AnmolSaini16/mapcn
synced 2026-04-25 16:14:54 +02:00
refactor(docs): simplify previews and polish navigation/layout styling
This commit is contained in:
File diff suppressed because one or more lines are too long
@@ -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 />
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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>
|
||||
)}
|
||||
|
||||
@@ -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">
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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>
|
||||
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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} />
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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>
|
||||
);
|
||||
|
||||
@@ -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>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -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}
|
||||
|
||||
@@ -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>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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>
|
||||
);
|
||||
|
||||
@@ -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"
|
||||
|
||||
@@ -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)}
|
||||
|
||||
@@ -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 />
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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"
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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>
|
||||
);
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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'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 "escaping" 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>
|
||||
|
||||
@@ -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];
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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>
|
||||
)}
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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 };
|
||||
|
||||
@@ -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}
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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,
|
||||
|
||||
Reference in New Issue
Block a user